Compare commits

...

238 Commits

Author SHA1 Message Date
Lorenz Hilpert
ddd1b81d0d Merge branch 'feature/responsive-customer-orders' into feature/responsive-customer-orders-breakpoints 2023-05-24 15:45:20 +02:00
Nino
c79a1cdad1 #2605 Focus Searchbox on Navigation Click - Finish first Version of Article Search Responsive Design - Reset customer Orders back to pre Responsive 2023-05-23 17:56:38 +02:00
Nino
edf978f5cf #3387 Hover Styling on Article Search Result List 2023-05-23 16:52:12 +02:00
Nino
4bfe35a8b9 Bugfix OrderBy Tablet Navigation 2023-05-23 16:07:46 +02:00
Nino
4b7c26b009 Update new Filter and Branch Selector Changes from ISA-Integration 2023-05-22 17:26:15 +02:00
Nino
2a442dde85 Filter Design angepasst 2023-05-17 16:33:11 +02:00
Nino
45bb39f466 Merge branch 'feature/responsive-customer-orders' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into feature/responsive-customer-orders 2023-05-17 16:26:18 +02:00
Nino
b2731432ed #4009 Alle Filter Entfernen eingebaut in Artikelsuche Filter Overlay 2023-05-17 16:25:46 +02:00
Nino Righi
8c424bab43 Merge branch 'feature/responsive-customer-orders' into feature/responsive-customer-orders-breakpoints 2023-05-16 16:33:54 +02:00
Nino Righi
571c748a66 Show Outlet Bugfix 2023-05-16 16:26:14 +02:00
Nino Righi
c104f36255 Updated Breakpoint, set MaxWidth to 1920px 2023-05-16 14:31:28 +02:00
Lorenz Hilpert
672245c467 Breakpoints RD 2023-05-16 10:54:47 +02:00
Nino Righi
0ae92e34c6 Breakpoints Handling Improved 2023-05-15 18:44:03 +02:00
Nino Righi
ce72ce48b2 New Breakpoints Update 2023-05-12 17:48:27 +02:00
Nino Righi
bd5ec27425 Merge branch 'develop' into feature/responsive-customer-orders 2023-05-11 16:54:54 +02:00
Lorenz Hilpert
4dc98f7980 Shell Styling 2023-05-11 16:42:37 +02:00
Lorenz Hilpert
c89ee18db3 Update env service added matcher for screen sizes 2023-05-11 16:10:05 +02:00
Lorenz Hilpert
c4aa7999a6 Shell auf max breite 1440px und zentriert 2023-05-11 15:41:09 +02:00
Nino Righi
27a83242c5 Filter Adjustment and Bugfix 2023-05-09 18:01:56 +02:00
Nino Righi
7bf23c4bcc Improved Routing and QueryParams handling 2023-05-09 17:34:30 +02:00
Nino Righi
0bc80e12aa #4000 Reset Selected Branch on Shell Navigation click, Product Search and Customer Orders 2023-05-09 14:40:44 +02:00
Nino Righi
b586e3da9e Responsive Design - Tablet Styling Bugfixes and Adjustments - Filter Layout - Container Heights - Routing Bugfixes 2023-05-08 17:09:15 +02:00
Nino Righi
a433f2cfe4 Responsive Design Article Result List Add Item To Cart via Select Bullets 2023-05-05 16:50:21 +02:00
Nino Righi
ff368d68b7 Responsive Design Order by Ipad fix 2023-05-04 15:09:05 +02:00
Nino Righi
40ebd72263 Article Search Results Searchbox Responsive Design and Filter Box Shadow Fix 2023-05-03 17:00:11 +02:00
Nino Righi
11eca1e25a Added new Icons, Fixed Searchbox Autocomplete 2023-05-03 16:20:12 +02:00
Nino Righi
b8d0153232 Filter Responsive Design 2023-05-02 18:08:18 +02:00
Nino Righi
0a195e78e5 OrderBy Responsive Design 2023-05-02 15:37:48 +02:00
Nino Righi
97c26f5fa1 Finished Article Details Styling and Started Filter Responsive Design 2023-04-28 18:24:57 +02:00
Nino Righi
8dac64d5b8 Merge branch 'develop' into feature/responsive-customer-orders 2023-04-28 10:21:46 +02:00
Nino Righi
2c295b0797 Article Details Page Responsive Design Layout and Styling Refactor 2023-04-27 18:31:28 +02:00
Lorenz Hilpert
696015b6a4 Styling for Searchbox 2023-04-27 18:08:18 +02:00
Lorenz Hilpert
f2f70e1d83 Searchbox Update 2023-04-27 17:14:49 +02:00
Lorenz Hilpert
4d1dbaa2f3 Moved searchbox and filter to shared 2023-04-27 14:26:50 +02:00
Nino Righi
4344f4617c Merge branch 'develop' into feature/responsive-customer-orders 2023-04-27 10:56:36 +02:00
Nino Righi
c451d2b329 Update Styling Catalog Main and Results fullscreen 2023-04-26 18:00:14 +02:00
Nino Righi
96d1a4b826 Page Catalog Layout Changes 2023-04-26 15:55:07 +02:00
Lorenz Hilpert
595bb27d99 Shell Max Content Width 2023-04-26 10:42:15 +02:00
Nino Righi
958a388fb5 Updated Navigation Service Implementation, Unit Test Fixes 2023-04-25 17:09:13 +02:00
Nino Righi
a908767f08 Merge branch 'develop' into feature/responsive-customer-orders 2023-04-24 17:41:36 +02:00
Nino Righi
3c0406031a Process, Breadcrumb, Shell Article Search Navigation Update 2023-04-24 16:47:31 +02:00
Lorenz Hilpert
7b72532c9e Added Config - Missing Icons 2023-04-24 16:31:25 +02:00
Lorenz Hilpert
6b756fe893 Merge branch 'release/2.3' into develop 2023-04-24 15:35:34 +02:00
Lorenz Hilpert
54c7f51766 #3008 - Fix Process Tab Close 2023-04-24 15:34:29 +02:00
Lorenz Hilpert
94b787655e #3008 Navigation durch Bereich wechseln CTA 2023-04-24 15:29:58 +02:00
Lorenz Hilpert
259d0f1648 #3999 Kaufoptionen - Anzeigefehler in Titel/Format 2023-04-24 15:19:52 +02:00
Lorenz Hilpert
872d3ff383 #3391 - Filiale mit Klick auswählen 2023-04-24 14:57:16 +02:00
Lorenz Hilpert
cf1c4d37b9 Update Package Lock 2023-04-24 14:45:53 +02:00
Lorenz Hilpert
ada16bac6c Merge branch 'develop' into feature/rd-navigation-shell 2023-04-24 14:35:31 +02:00
Michael Auer
d68055f8a9 ~ Version Bump: 2.3 2023-04-24 11:52:12 +02:00
Michael Auer
8d98dcf7e7 Merge tag '2.2' into develop 2023-04-24 11:16:13 +02:00
Michael Auer
c828a69f66 Merge branch 'release/2.2' 2023-04-24 11:16:04 +02:00
Lorenz Hilpert
eefb6062c7 removed fdescribed 2023-04-21 18:49:57 +02:00
Lorenz Hilpert
470a451168 Unit Tests for ShellSideMenuComponent 2023-04-21 18:49:37 +02:00
Lorenz Hilpert
e3b018c5f7 remove fdescribe 2023-04-21 17:45:12 +02:00
Lorenz Hilpert
bd695e21d4 RD Navigation Unit Tests - ProcessBar 2023-04-21 17:44:49 +02:00
Nino Righi
79bb9b8c11 Routing and Breadcrumb Update Article Search, Created Navigation Service 2023-04-21 17:12:55 +02:00
Lorenz Hilpert
6311ebe467 Show Navigation for Package-inspection 2023-04-21 10:33:34 +02:00
Nino Righi
341b202bc4 Kundenbestellungen Zwischencommit 2023-04-20 18:10:00 +02:00
Lorenz Hilpert
f8a5ceed97 #3391 Fix - Tablet Kaufoptionen PopUp 2023-04-20 13:55:21 +02:00
Lorenz Hilpert
0420bda5da Bugfix - Default Branch in Kaufoptionen 2023-04-20 11:07:45 +02:00
Lorenz Hilpert
6dc532f40e Merge branch 'develop' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into develop 2023-04-20 08:31:50 +02:00
Lorenz Hilpert
0560d01f30 Merge branch 'release/2.2' into develop 2023-04-20 08:31:42 +02:00
Lorenz Hilpert
bc67ec3287 URL Change paragon-data -> paragon-systems.de 2023-04-20 08:28:54 +02:00
Lorenz Hilpert
bed35a2377 URL Change wws 2023-04-20 08:10:45 +02:00
Nino Righi
f284dc1db5 Article Search and Customer Order Filter Navigation Update Based on previous Routes, Updated Routing on Tablet 2023-04-18 17:36:08 +02:00
Lorenz Hilpert
aaf156cee3 Merge branch 'develop' into feature/rd-navigation-shell 2023-04-18 14:11:34 +02:00
Lorenz Hilpert
e8020ffde6 RD Shell - Navigation 2023-04-18 14:09:36 +02:00
Nino Righi
8d6bd80902 Merged PR 1522: #3989 Hotfix Remission List Item Loading Spinner
#3989 Hotfix Remission List Item Loading Spinner
2023-04-18 11:24:24 +00:00
Nino Righi
8aa870dddd Merged PR 1521: #3506 Fix Removed PackageCode from Payload of ReturnFinalizeReceipt
#3506 Fix Removed PackageCode from Payload of ReturnFinalizeReceipt
2023-04-18 08:44:40 +00:00
Nino Righi
26a3c76d5f Merged PR 1520: #3906 Fix Instock Bug Article Search Results
#3906 Fix Instock Bug Article Search Results
2023-04-17 12:25:29 +00:00
Lorenz Hilpert
18f738f2c3 Bereich Wareneingang aus Navigation entfernt 2023-04-17 11:07:50 +02:00
Lorenz Hilpert
bba50ccbcc Merge branch 'release/2.2' into develop 2023-04-13 14:11:53 +02:00
Lorenz Hilpert
3eea3b913d #3688 PopUp mit Irrläufern - Navigation disabled 2023-04-13 14:10:48 +02:00
Lorenz Hilpert
2dbeec831e #3976 nur ein Titel auf einmal remittieren 2023-04-13 14:00:09 +02:00
Lorenz Hilpert
88a06628e3 #3973 added missing tabindex 2023-04-12 18:24:07 +02:00
Lorenz Hilpert
37ceb30ffb #3973 Bedienung per Tastatur funktioniert fehlerhaft 2023-04-12 13:23:48 +02:00
Lorenz Hilpert
ebd0515e96 #3978 instock-Anfrage wird nicht immer ausgeführt 2023-04-11 16:41:00 +02:00
Nino Righi
ece5d0fa0d Merged PR 1519: #3957 Fix Develop Branch Selector Implementation with new purchasing options...
#3957 Fix Develop Branch Selector Implementation with new purchasing options modal
2023-04-11 14:26:23 +00:00
Lorenz Hilpert
6be214c6cd #3977 Fehlermeldung erscheint an falscher Stelle 2023-04-11 16:25:23 +02:00
Nino Righi
2144ec838c Customer Order Splitscreen Navigation Update 2023-04-06 16:12:29 +02:00
Lorenz Hilpert
c470453ea4 Merge branch 'release/2.2' into develop 2023-04-05 11:58:29 +02:00
Nino Righi
bbe9326954 Merged PR 1518: #3957 Filter Current Branch if Role is Store
#3957 Filter Current Branch if Role is Store
2023-04-05 09:55:41 +00:00
Lorenz Hilpert
27961bb4e5 Checkbox Styling Kaufoptionen 2023-04-05 11:09:41 +02:00
Lorenz Hilpert
4952c090ef #3975 Anzahl der Irrläufer in der Liste auf 10 gesetzt 2023-04-05 11:06:17 +02:00
Nino Righi
bff10cb2ff Customer Order Changes 2023-04-05 11:04:38 +02:00
Nino Righi
d303b1444b Zoom Update and Article Result List Styling Update 2023-04-03 17:54:08 +02:00
Lorenz Hilpert
74e4016625 #3928 Kaufoptionen - Filialen die IS_ORDERING_ENABLED true sind können ausgewählt werden 2023-04-03 13:14:58 +02:00
Lorenz Hilpert
c389008811 #3967 Fix - Nicht bestellbaren Artikel löschen führt zu Fehlermeldung 2023-04-03 10:21:21 +02:00
Nino Righi
e6dcf22012 Merged PR 1516: #3957 Filialdropdown Sortierung mit Store Rolle
#3957 Filialdropdown Sortierung mit Store Rolle
2023-03-31 15:14:30 +00:00
Nino Righi
d067f925b9 Merged PR 1517: #3927 #3929 #3964 Remission Wannennummer Changes
#3927 #3929 #3964 Remission Wannennummer Changes
2023-03-31 15:05:21 +00:00
Lorenz Hilpert
8b5609f765 #3672 Detailseite 'Falsche Filiale' - Erstes Item wird bei einem Irrläufer auch verblasst angezeigt 2023-03-31 14:08:52 +02:00
Lorenz Hilpert
dd04a1f2af #3963 Fix Falsche Verfügbarkeiten 2023-03-31 14:05:15 +02:00
Lorenz Hilpert
9caabb6cc0 #3960 Buttons Alle auswählen and Alle abwählen will now work correctly. 2023-03-31 13:34:00 +02:00
Nino Righi
33b28d5f41 Merged PR 1515: #3244 Remission Removed "Are you sure you want to remit" - Popup
#3244 Remission Removed "Are you sure you want to remit" - Popup
2023-03-31 11:21:28 +00:00
Lorenz Hilpert
475f9b5e34 Mark PurchaseOptionsModalComponent Fetching if Availabilities are being fetched 2023-03-31 11:29:42 +02:00
Lorenz Hilpert
60f1348ea5 Stock Request Verhindern 2023-03-31 11:10:46 +02:00
Lorenz Hilpert
533b6e1fcf Unit Test Fix 2023-03-31 10:21:38 +02:00
Lorenz Hilpert
8961730b74 #3672 Irrlauefer Details Anpassung 2023-03-30 18:26:51 +02:00
Lorenz Hilpert
5d580714c8 #3953 Minusbestände als Nullbestand anzeigt 2023-03-30 18:08:05 +02:00
Lorenz Hilpert
daf1ead75b #3962 Anrede ist nicht mehr Pflichtfeld 2023-03-30 14:24:12 +02:00
Lorenz Hilpert
aef2654a39 #3962 Anrede ist nicht mehr Pflichtfeld 2023-03-30 13:41:22 +02:00
Lorenz Hilpert
8243cd3528 #3947 PopUp wird nach unten nicht vollständig angezeigt 2023-03-30 11:41:37 +02:00
Lorenz Hilpert
447456d7a6 #3946 Anzeige lieferbare Menge nur in Popup 2023-03-30 11:22:07 +02:00
Lorenz Hilpert
241a34d7a8 Merge branch 'release/2.2' into develop 2023-03-30 11:04:04 +02:00
Lorenz Hilpert
4e67b2e8b9 #3936 - Kundenbestellung Leere Edit Seite
(cherry picked from commit 83406277ad)
2023-03-30 10:54:41 +02:00
Lorenz Hilpert
8bc2ea8373 #3385 Preiseingabe Geschenkkarte 2023-03-30 10:39:40 +02:00
Nino Righi
00a6a113c8 Merged PR 1514: #3956 Hotfix PDP Wording Change Branch Availability
#3956 Hotfix PDP Wording Change Branch Availability
2023-03-30 08:35:25 +00:00
Nino Righi
dc04619128 #3948 Hotfix HSC Kundenbestellungen Details Header Anzeige 2023-03-29 15:12:35 +02:00
Nino Righi
150e7965ee Zwischencommit 2023-03-29 12:57:05 +02:00
Lorenz Hilpert
e066da3762 #3386 Canadd Wurde fuer Download Artikel falsch hinterlegt 2023-03-29 11:54:19 +02:00
Lorenz Hilpert
ad96278956 #3688 List Styling angepasst 2023-03-29 10:29:32 +02:00
Nino Righi
3fcf3d9396 Responsive Design Splitscreen Article Search Results Update 2023-03-28 15:08:31 +02:00
Lorenz Hilpert
83406277ad #3936 - Kundenbestellung Leere Edit Seite 2023-03-28 14:40:49 +02:00
Lorenz Hilpert
9e89348381 #3958 Wahl Rücklage Kaufoption wirft Fehler 2023-03-28 14:10:25 +02:00
Lorenz Hilpert
0fb7419598 #3385 HFI Gutschein 2023-03-27 11:19:09 +02:00
Nino Righi
52278b8baf Initial Responsive Design Implementation based on Article Search 2023-03-24 17:19:59 +01:00
Lorenz Hilpert
1790298cb4 #3931 Löschen von Positionen in der Kaufoption 2023-03-24 11:28:53 +01:00
Lorenz Hilpert
ffe8e39c85 #3934 - Anzeige Message Wenn Keine Verfügbarkeiten Existieren 2023-03-24 11:20:14 +01:00
Lorenz Hilpert
67e0f4bd46 #3945 Reinfolge von verfügbar als-Daten ist nicht fest 2023-03-23 19:24:22 +01:00
Lorenz Hilpert
e7b3a58da3 #3946 Fehler Lieferbare Exemplare 2023-03-23 19:20:24 +01:00
Lorenz Hilpert
598f9f3777 #3385 HFI Gutschein Anzeige Kaufoptionen 2023-03-23 18:25:51 +01:00
Lorenz Hilpert
0a7dca2e12 Packstück Kontoller aktivieren 2023-03-23 10:09:48 +01:00
Lorenz Hilpert
4b342778df #3365 Navigation angepasst 2023-03-22 15:32:20 +01:00
Lorenz Hilpert
10e8fd904a #3933 2023-03-22 14:29:57 +01:00
Lorenz Hilpert
1de342fd3b Update Add To Shopping Cart from PDP
(cherry picked from commit 4a5dd23561fdec458447c19c93faa1654ab80c7c)
2023-03-21 11:21:35 +01:00
Lorenz Hilpert
f4c1c3dd7f Merged PR 1513: Kaufoptionen
Related work items: #3365, #3366, #3385, #3386, #3391
2023-03-20 17:11:53 +00:00
Nino Righi
80bfc59356 Merged PR 1500: #3506 Remission Wanne Scanner nach Start der Remission
#3506 Remission Wanne Scanner nach Start der Remission
2023-03-17 09:25:43 +00:00
Lorenz Hilpert
3796f3ed5f Authentication Config Update 2023-03-16 15:30:01 +01:00
Lorenz Hilpert
f2e03d22d8 Merge branch 'develop' into release/2.2 2023-03-16 15:28:36 +01:00
Nino Righi
ef967b66e8 Merged PR 1512: #3887 Removed Scrollbar until Responsive Design
#3887 Removed Scrollbar until Responsive Design
2023-03-16 13:59:47 +00:00
Lorenz Hilpert
58ea70cc6c Merge branch 'develop' into release/2.2 2023-03-15 17:42:51 +01:00
Nino Righi
e4823950df Merged PR 1511: #3887 HSC Details Kundenbestellungen 360 Grad
#3887 HSC Details Kundenbestellungen 360 Grad
2023-03-15 16:41:57 +00:00
Lorenz Hilpert
8f9923ba5d #3673 ArrivalStatus Prüfung 2023-03-14 18:22:17 +01:00
Lorenz Hilpert
72bbd2c36e Checkbox Styling with Sub Options 2023-03-14 14:15:07 +01:00
Lorenz Hilpert
9bdb902a56 #3673 #3905 2023-03-14 13:55:38 +01:00
Nino Righi
bbc2e55ae3 Merged PR 1510: #3878 Updated Section Toggle if Role CallCenter is Active
#3878 Updated Section Toggle if Role CallCenter is Active
2023-03-14 09:16:44 +00:00
Nino Righi
6995bdb527 Merged PR 1508: Filter Doku Readme angelegt mit 2 konkreten Anwendungsbeispielen der Filter I...
Filter Doku Readme angelegt mit 2 konkreten Anwendungsbeispielen der Filter IST Implementierung

Co-authored-by: lorenzh <lorenzh@users.noreply.github.com>
2023-03-13 15:43:59 +00:00
Nino Righi
a7abc35316 Merged PR 1509: #3899 Fix Customer Orders loadItems request with compartmentCode
#3899 Fix Customer Orders loadItems request with compartmentCode
2023-03-10 14:28:29 +00:00
Lorenz Hilpert
772aed597b Wareneingang ausblenden 2023-03-10 08:05:37 +01:00
Lorenz Hilpert
9e18825c27 Merge branch 'develop' into release/2.2 2023-03-10 08:04:44 +01:00
Lorenz Hilpert
27b7ffcf99 #3898 Trefferliste wird in anderen Vorgang übernommen 2023-03-09 14:44:17 +01:00
Nino Righi
d804a744b6 Merged PR 1506: #3895 Purchaseing Options Modal Selected branch check if correct branchType i...
#3895 Purchaseing Options Modal Selected branch check if correct branchType is selected
2023-03-08 19:29:49 +00:00
Nino Righi
fc5cf27bd1 Merged PR 1507: #3882 Fix Hide Select Bullets if items have no actions
#3882 Fix Hide Select Bullets if items have no actions
2023-03-08 19:24:42 +00:00
Lorenz Hilpert
355bba8966 Merge branch 'ipad-bugfix' into develop 2023-03-08 20:22:02 +01:00
Lorenz Hilpert
e31c5f5a19 IPad Bugfix Scanner 2023-03-08 20:03:43 +01:00
Lorenz Hilpert
22837bbf8d IPAd Test Logging 2023-03-08 19:45:25 +01:00
Lorenz Hilpert
6324486dca ipad test 4 2023-03-08 19:21:33 +01:00
Lorenz Hilpert
653100f539 IPad test 4 2023-03-08 19:21:14 +01:00
Lorenz Hilpert
d671ba583d IPad Test 3 2023-03-08 19:20:00 +01:00
Lorenz Hilpert
fba324c6cb IPad fIx test 2 2023-03-08 18:58:02 +01:00
Lorenz Hilpert
ccbec5bcff Scanner test 2023-03-08 18:39:27 +01:00
Lorenz Hilpert
da1978bf21 Wareneingang in der Navigation Aktiviert 2023-03-08 16:01:41 +01:00
Lorenz Hilpert
1672b89775 Message Event Logs 2023-03-08 15:35:31 +01:00
Lorenz Hilpert
d9a9db6ec8 Added Debug Page for IPad 2023-03-08 15:01:17 +01:00
Lorenz Hilpert
0882bc2ca7 Merge branch 'develop' into release/2.2 2023-03-08 13:15:49 +01:00
Nino Righi
89b8d07bb4 Merged PR 1503: #3876 Fix Reorder Modal with Selected Branch now returns correct availabiliti...
#3876 Fix Reorder Modal with Selected Branch now returns correct availabilities based by selected branch
2023-03-08 12:15:07 +00:00
Nino Righi
c65c8edd2d Merged PR 1504: #3891 Fix Tooltip PDP and Articlesearch Availability by Branch
#3891 Fix Tooltip PDP and Articlesearch Availability by Branch
2023-03-08 12:09:08 +00:00
Nino Righi
5f7ce96919 Merged PR 1505: #3896 Assortment Price Update Item Added "x" to stock value
#3896 Assortment Price Update Item Added "x" to stock value
2023-03-08 12:06:13 +00:00
Lorenz Hilpert
d9a2601f75 Merge branch 'develop' into release/2.2 2023-03-08 11:37:49 +01:00
Lorenz Hilpert
37a04cadf8 Added scandit Lizenz 2023-03-08 11:03:59 +01:00
Lorenz Hilpert
ca10c01398 #3360 Bestandsanzeige einer anderen Filiale 2023-03-06 17:37:36 +01:00
Nino Righi
24f6ba117d Merged PR 1502: #3358 Disable Pick Up Option in purchasing Modal if no valid targetBranch is...
#3358 Disable Pick Up Option in purchasing Modal if no valid targetBranch is selected
2023-03-06 14:36:31 +00:00
Lorenz Hilpert
7508144e27 #3886 Informationen nur für Filiale hinterlegen sessionstorage 2023-03-06 13:36:23 +01:00
Lorenz Hilpert
9f0fec8046 Auth Service Delay Window reload 2023-03-06 13:00:27 +01:00
Nino Righi
67d70fac8e Merged PR 1501: #3771 #3876 Customer Orders Select Bullets and Reorder Modal Bugfix
#3771 #3876 Customer Orders Select Bullets and Reorder Modal Bugfix
2023-03-06 09:35:48 +00:00
Lorenz Hilpert
1775f6fd89 #3773 Filter Zielfiliale 2023-03-03 14:56:42 +01:00
Lorenz Hilpert
438c367101 Fix Erneut anmelden 2023-03-03 14:34:17 +01:00
Lorenz Hilpert
cb9d8ffa91 Silent Refresh with code flow 2023-03-03 11:51:13 +01:00
Lorenz Hilpert
2ecb0c5cf6 Update Auth Token Refresh and Configs 2023-03-02 17:43:38 +01:00
Lorenz Hilpert
b01ce5b3b6 Merge branch 'develop' into release/2.2 2023-03-02 15:52:30 +01:00
Lorenz Hilpert
8a55d52b2b AuthService SilentRefresh Catch Error 2023-03-02 11:24:30 +01:00
Lorenz Hilpert
c7e6f00ddb Merge branch 'release/2.2' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into release/2.2 2023-03-02 10:58:39 +01:00
Lorenz Hilpert
85831ffe5d Config Fix Statging - wws API 2023-03-02 10:58:26 +01:00
Lorenz Hilpert
762a5a2072 Config Fix wws Endpoint 2023-03-02 10:57:30 +01:00
Nino Righi
405e1ed023 Merged PR 1498: #3774 Meldenummer und Branch Select Filter, Unit Test Fixes
#3774 Meldenummer und Branch Select Filter, Unit Test Fixes
2023-03-01 18:11:36 +00:00
Lorenz Hilpert
41177436d4 Silent Refresh Error - Catch Error 2023-03-01 19:04:25 +01:00
Nino Righi
d1fca976a2 Merged PR 1499: #3782 Fix Resetting Filter Settings Properly After Click On Product Search on...
#3782 Fix Resetting Filter Settings Properly After Click On Product Search on Navigation
2023-03-01 17:54:01 +00:00
Lorenz Hilpert
59cf407c26 #3759 Bearbeiten deaktivieren 2023-03-01 17:56:31 +01:00
Lorenz Hilpert
e95828a514 Improvements for Authentication and Silent Refresh 2023-03-01 15:46:01 +01:00
Lorenz Hilpert
8b915c7c83 Merge branch 'develop' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into develop 2023-02-28 11:40:15 +01:00
Lorenz Hilpert
ebc6a01b7a #3880 Branch Id in Autocomplete Request - Kundenbestellung 2023-02-28 11:40:05 +01:00
Lorenz Hilpert
8cad3c4c14 Merge branch 'release/2.2' into develop 2023-02-27 18:48:14 +01:00
Lorenz Hilpert
a5537c21a1 Merge branch 'develop' into release/2.2 2023-02-27 18:47:47 +01:00
Lorenz Hilpert
edf96434b7 Merge branch 'develop' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into develop 2023-02-27 18:33:08 +01:00
Lorenz Hilpert
257df95c72 #3876 Artikel nachbestellen - In ausgewählte Filiale 2023-02-27 18:32:55 +01:00
Nino Righi
a268df503a Merged PR 1497: #3871 Gelbe Seiten Fachbodenbeschriftung
#3871 Gelbe Seiten Fachbodenbeschriftung
2023-02-27 17:27:23 +00:00
Nino Righi
a57ccbe4c2 Merged PR 1495: #3875 Display Estimated Shipping Date if orderType is not 1
#3875 Display Estimated Shipping Date if orderType is not 1
2023-02-27 17:21:16 +00:00
Lorenz Hilpert
7f37771dc7 Merge branch 'develop' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into develop 2023-02-27 17:56:00 +01:00
Lorenz Hilpert
3a753dde83 Fix unit Test For AuthService 2023-02-27 17:55:52 +01:00
Nino Righi
8e91b1363b Merged PR 1496: #3357 Fixed Branches OrderBy
#3357 Fixed Branches OrderBy
2023-02-27 16:53:05 +00:00
Lorenz Hilpert
e77929ab89 #3357 Ausgewählte Filiae wird berücksichtigt 2023-02-27 17:38:09 +01:00
Lorenz Hilpert
02031e97e3 Minor Bugfixes 2023-02-27 17:20:28 +01:00
Lorenz Hilpert
ede1c505d4 #3872 Wording Anpassung 2023-02-27 14:03:13 +01:00
Lorenz Hilpert
5cd5b685d2 #3673 Farbanpassung - Wareneingang 2023-02-27 10:46:42 +01:00
Lorenz Hilpert
d856f1d1cc Fix Unit Test 2023-02-27 10:17:11 +01:00
Lorenz Hilpert
10bb912bda Kundenbestellung Anpassung API 2023-02-27 10:08:16 +01:00
Nino Righi
3b2135a570 Merged PR 1494: #3357 Implementierung Umkreissuche + Unit Test Fix
#3357 Implementierung Umkreissuche + Unit Test Fix
2023-02-24 16:46:31 +00:00
Lorenz Hilpert
9c5d209887 #3870 Load Spinner PP 2023-02-24 17:27:54 +01:00
Lorenz Hilpert
763a770bcf Skip Unit Test 2023-02-24 14:58:29 +01:00
Lorenz Hilpert
f8f3456ba3 #3790 Filialsuche hinzugefügt 2023-02-24 14:51:05 +01:00
Lorenz Hilpert
4da7f02cf7 #3790 Kundenbestellungen 2023-02-24 13:42:58 +01:00
Nino Righi
4772e24c78 Merged PR 1493: #3865 Assortment OrderBy Implementation
#3865 Assortment OrderBy Implementation
2023-02-24 10:45:32 +00:00
Lorenz Hilpert
c3a9b82abb #3867 Preisänderungen - Scrollbalken fehlt 2023-02-23 10:03:54 +01:00
Lorenz Hilpert
4716940708 #3868 Button überdeckt letzte Kachel 2023-02-23 10:02:25 +01:00
Lorenz Hilpert
0eb09e2dbb Merge branch 'develop' into release/2.2 2023-02-22 16:38:42 +01:00
Lorenz Hilpert
111a33b12f #3805 Liste Weitere Verfügbarkeiten 2023-02-22 14:08:26 +01:00
Lorenz Hilpert
bcff2272ab #3853 Gelbe Seiten - Auf Erledigt gesetzte Posten bleiben auf die Liste 2023-02-22 13:30:58 +01:00
Lorenz Hilpert
f30ae91854 Merge branch 'release/2.2' into develop 2023-02-21 10:26:27 +01:00
Lorenz Hilpert
7eaad843a9 Merge branch 'develop' into release/2.2 2023-02-21 10:26:01 +01:00
Lorenz Hilpert
cb367d32c3 #3854 Filter in Sortiment und Wareineingang reagieren nicht einheitlich mit anderen Filtern 2023-02-20 18:15:27 +01:00
Lorenz Hilpert
b4cf88bd54 Bugfix Filter 2023-02-20 18:02:16 +01:00
Lorenz Hilpert
b8097fcd3a Filter Verhalten angepasst 2023-02-20 18:00:15 +01:00
Lorenz Hilpert
6db2238096 Item Update and Error Message for Packstück Kontrolle 2023-02-20 16:51:52 +01:00
Lorenz Hilpert
cd426d5534 #3859 Navigation Tätigkeitskalender 2023-02-20 14:41:26 +01:00
Lorenz Hilpert
c214d47aad Null Check 2023-02-20 13:58:10 +01:00
Lorenz Hilpert
c09c44ec5f Merge branch 'develop' into release/2.2 2023-02-20 13:09:44 +01:00
Lorenz Hilpert
f9f6d0d836 Merged PR 1492: Packstück Kontrolle
Related work items: #3688, #3690, #3816
2023-02-20 12:09:01 +00:00
Nino Righi
74a6c75c21 Merged PR 1490: #3848 IPAD2 Removed Grid and added Flex on Scroll Arrow inside Filter Input O...
#3848 IPAD2 Removed Grid and added Flex on Scroll Arrow inside Filter Input Options
2023-02-17 13:49:55 +00:00
Nino Righi
526ebc77bc Merged PR 1491: #3849 Improvement Sortiment Filter Display Hint
#3849 Improvement Sortiment Filter Display Hint
2023-02-17 13:49:47 +00:00
Lorenz Hilpert
addac44c0f Merge branch 'develop' into release/2.2 2023-02-17 14:05:46 +01:00
Lorenz Hilpert
303d575fde config.json - scope hinzugefuegt - isa-wws-webapi 2023-02-17 14:02:24 +01:00
Lorenz Hilpert
bf8438b229 Version Bump 2.2 2023-02-17 13:37:16 +01:00
Nino Righi
ea8bbafbfa Merged PR 1489: Small fixes
Small fixes
2023-02-17 09:28:50 +00:00
Nino Righi
14815e79d5 Merged PR 1488: #3354 #3814 Ipad styling bugfixes, check environment and native container update
#3354 #3814 Ipad styling bugfixes, check environment and native container update
2023-02-16 16:45:58 +00:00
Nino Righi
4a3de35224 Merged PR 1486: #3524 History Added Word Break normal on caption
#3524 History Added Word Break normal on caption
2023-02-16 15:55:29 +00:00
Nino Righi
974f549c31 Merged PR 1487: #3836 #3837 Gelbe Seiten IPAD Styling fixes and Filter Scrolling
#3836 #3837 Gelbe Seiten IPAD Styling fixes and Filter Scrolling
2023-02-16 15:55:20 +00:00
Lorenz Hilpert
76596939c5 #3794 Submit mit Enter und Lupen-Symbol führt zu unterschiedliche Ergebnisse 2023-02-16 11:31:24 +01:00
Nino Righi
138974bca7 Merged PR 1485: #3359 Removed resetSelectedBranch logic on navigation click
#3359 Removed resetSelectedBranch logic on navigation click
2023-02-14 14:58:47 +00:00
Nino Righi
02aee02694 Merged PR 1482: #3813 Fix IPAD2 Added web-animations-js polyfill
#3813 Fix IPAD2 Added web-animations-js polyfill
2023-02-14 11:01:29 +00:00
Nino Righi
e2ada75611 Merged PR 1483: #3806 IPAD2 Styling Branch Dropdown
#3806 IPAD2 Styling Branch Dropdown
2023-02-14 11:01:24 +00:00
Nino Righi
78b757c55b Merged PR 1484: #3828 Bugfixes Disable Print
#3828 Bugfixes Disable Print
2023-02-14 11:01:15 +00:00
Lorenz Hilpert
dfd273e7bf Build Fehler Fix 2023-02-13 17:52:32 +01:00
Nino Righi
1a72c23412 Merged PR 1481: Gelbe Seiten - Sortiment - Preisänderung Implementation + Init Unit Tests
Gelbe Seiten - Sortiment - Preisänderung Implementation + Init Unit Tests
2023-02-13 16:42:08 +00:00
Michael Auer
55a92ad029 Merge tag '3754-lieferschein-erneut-drucken' into develop 2023-02-13 17:28:42 +01:00
Michael Auer
aea6a0d131 Merge branch 'hotfix/3754-lieferschein-erneut-drucken' 2023-02-13 17:11:39 +01:00
Lorenz Hilpert
e76e031675 Merged PR 1480: #3806 Suchfeld wird nun als geschlossen erkannt und stylings greifen wieder
#3806 Suchfeld wird nun als geschlossen erkannt und stylings greifen wieder
2023-02-08 09:54:55 +00:00
Lorenz Hilpert
dc42107668 #3754 Lieferschein erneut drucken - CTA Fix 2023-01-17 16:11:53 +01:00
849 changed files with 22831 additions and 11254 deletions

View File

@@ -3,6 +3,5 @@
"johnpapa.angular2",
"esbenp.prettier-vscode",
"angular.ng-template",
"eg2.vscode-npm-script"
]
}

View File

@@ -375,37 +375,6 @@
}
}
},
"@shell/breadcrumb": {
"projectType": "library",
"root": "apps/shell/breadcrumb",
"sourceRoot": "apps/shell/breadcrumb/src",
"prefix": "shell",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"tsConfig": "apps/shell/breadcrumb/tsconfig.lib.json",
"project": "apps/shell/breadcrumb/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/shell/breadcrumb/tsconfig.lib.prod.json"
}
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"tsConfig": "apps/shell/breadcrumb/tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"polyfills": [
"zone.js",
"zone.js/testing"
]
}
}
}
},
"@domain/defs": {
"projectType": "library",
"root": "apps/domain/defs",
@@ -840,37 +809,6 @@
}
}
},
"@shell/header": {
"projectType": "library",
"root": "apps/shell/header",
"sourceRoot": "apps/shell/header/src",
"prefix": "shell",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"tsConfig": "apps/shell/header/tsconfig.lib.json",
"project": "apps/shell/header/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/shell/header/tsconfig.lib.prod.json"
}
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"tsConfig": "apps/shell/header/tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"polyfills": [
"zone.js",
"zone.js/testing"
]
}
}
}
},
"@modal/reorder": {
"projectType": "library",
"root": "apps/modal/reorder",
@@ -1058,74 +996,6 @@
}
}
},
"@shell/footer": {
"projectType": "library",
"root": "apps/shell/footer",
"sourceRoot": "apps/shell/footer/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "apps/shell/footer/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/shell/footer/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "apps/shell/footer/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"tsConfig": "apps/shell/footer/tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"polyfills": [
"zone.js",
"zone.js/testing"
]
}
}
}
},
"@shell/process": {
"projectType": "library",
"root": "apps/shell/process",
"sourceRoot": "apps/shell/process/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "apps/shell/process/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/shell/process/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "apps/shell/process/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"tsConfig": "apps/shell/process/tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"polyfills": [
"zone.js",
"zone.js/testing"
]
}
}
}
},
"@domain/isa": {
"projectType": "library",
"root": "apps/domain/isa",
@@ -1160,40 +1030,6 @@
}
}
},
"@shell/filter-overlay": {
"projectType": "library",
"root": "apps/shell/filter-overlay",
"sourceRoot": "apps/shell/filter-overlay/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "apps/shell/filter-overlay/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/shell/filter-overlay/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "apps/shell/filter-overlay/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"tsConfig": "apps/shell/filter-overlay/tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"polyfills": [
"zone.js",
"zone.js/testing"
]
}
}
}
},
"@store/search-component-store": {
"projectType": "library",
"root": "apps/store/search-component-store",
@@ -1600,6 +1436,76 @@
}
}
}
},
"@domain/product-list": {
"projectType": "library",
"root": "apps/domain/product-list",
"sourceRoot": "apps/domain/product-list/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "apps/domain/product-list/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/domain/product-list/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "apps/domain/product-list/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"tsConfig": "apps/domain/product-list/tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"polyfills": [
"zone.js",
"zone.js/testing"
]
}
}
}
},
"shell": {
"projectType": "library",
"root": "apps/shell",
"sourceRoot": "apps/shell/src",
"prefix": "shell",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "apps/shell/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/shell/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "apps/shell/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"tsConfig": "apps/shell/tsconfig.spec.json",
"polyfills": [
"zone.js",
"zone.js/testing"
]
}
}
}
}
},
"cli": {
"analytics": false
}
}

View File

@@ -12,7 +12,7 @@ export class NativeScanAdapter implements ScanAdapter {
init(): Promise<boolean> {
return new Promise((resolve, reject) => {
resolve(this.nativeContainerService.isUiWebview().isNative);
resolve(this.nativeContainerService.isNative);
});
}

View File

@@ -1,10 +1,9 @@
import { Injectable } from '@angular/core';
import { DomainAvailabilityService } from '@domain/availability';
import { Store } from '@ngrx/store';
import { BranchDTO } from '@swagger/checkout';
import { isBoolean, isNumber } from '@utils/common';
import { BehaviorSubject, Observable } from 'rxjs';
import { first, map } from 'rxjs/operators';
import { first, map, switchMap } from 'rxjs/operators';
import { ApplicationProcess } from './defs';
import {
removeProcess,
@@ -16,25 +15,30 @@ import {
selectActivatedProcess,
patchProcess,
patchProcessData,
selectTitle,
setTitle,
} from './store';
@Injectable()
export class ApplicationService {
/** @deprecated */
private activatedProcessIdSubject = new BehaviorSubject<number>(undefined);
/** @deprecated */
get activatedProcessId() {
return this.activatedProcessIdSubject.value;
}
/** @deprecated */
get activatedProcessId$() {
return this.activatedProcessIdSubject.asObservable();
}
title$ = this.store.select(selectTitle);
constructor(private store: Store) {}
setTitle(title: string) {
this.store.dispatch(setTitle({ title }));
}
getProcesses$(section?: 'customer' | 'branch') {
const processes$ = this.store.select(selectProcesses);
return processes$.pipe(map((processes) => processes.filter((process) => (section ? process.section === section : true))));
@@ -70,7 +74,13 @@ export class ApplicationService {
this.store.dispatch(patchProcessData({ processId, data }));
}
getSelectedBranch$(processId: number): Observable<BranchDTO> {
getSelectedBranch$(processId?: number): Observable<BranchDTO> {
if (!processId) {
return this.activatedProcessId$.pipe(
switchMap((processId) => this.getProcessById$(processId).pipe(map((process) => process?.data?.selectedBranch)))
);
}
return this.getProcessById$(processId).pipe(map((process) => process?.data?.selectedBranch));
}

View File

@@ -3,6 +3,8 @@ import { ApplicationProcess } from '..';
const prefix = '[CORE-APPLICATION]';
export const setTitle = createAction(`${prefix} Set Title`, props<{ title: string }>());
export const setSection = createAction(`${prefix} Set Section`, props<{ section: 'customer' | 'branch' }>());
export const addProcess = createAction(`${prefix} Add Process`, props<{ process: ApplicationProcess }>());

View File

@@ -1,9 +1,18 @@
import { Action, createReducer, on } from '@ngrx/store';
import { setSection, addProcess, removeProcess, setActivatedProcess, patchProcess, patchProcessData } from './application.actions';
import {
setSection,
addProcess,
removeProcess,
setActivatedProcess,
patchProcess,
patchProcessData,
setTitle,
} from './application.actions';
import { ApplicationState, INITIAL_APPLICATION_STATE } from './application.state';
const _applicationReducer = createReducer(
INITIAL_APPLICATION_STATE,
on(setTitle, (state, { title }) => ({ ...state, title })),
on(setSection, (state, { section }) => ({ ...state, section })),
on(addProcess, (state, { process }) => ({ ...state, processes: [...state.processes, { data: {}, ...process }] })),
on(removeProcess, (state, { processId }) => {

View File

@@ -2,6 +2,8 @@ import { createFeatureSelector, createSelector } from '@ngrx/store';
import { ApplicationState } from './application.state';
export const selectApplicationState = createFeatureSelector<ApplicationState>('core-application');
export const selectTitle = createSelector(selectApplicationState, (s) => s.title);
export const selectSection = createSelector(selectApplicationState, (s) => s.section);
export const selectProcesses = createSelector(selectApplicationState, (s) => s.processes);

View File

@@ -1,11 +1,13 @@
import { ApplicationProcess } from '../defs';
export interface ApplicationState {
title: string;
processes: ApplicationProcess[];
section: 'customer' | 'branch';
}
export const INITIAL_APPLICATION_STATE: ApplicationState = {
title: '',
processes: [],
section: 'customer',
};

View File

@@ -1,7 +1,11 @@
import { ModuleWithProviders, NgModule } from '@angular/core';
import { AuthService } from './auth.service';
import { OAuthModule } from 'angular-oauth2-oidc';
@NgModule({})
import { IfRoleDirective } from './if-role.directive';
@NgModule({
declarations: [IfRoleDirective],
exports: [IfRoleDirective],
})
export class AuthModule {
static forRoot(): ModuleWithProviders<AuthModule> {
return {

View File

@@ -1,9 +1,10 @@
import { coerceArray, coerceStringArray } from '@angular/cdk/coercion';
import { Injectable } from '@angular/core';
import { Config } from '@core/config';
import { isNullOrUndefined } from '@utils/common';
import { AuthConfig, OAuthService } from 'angular-oauth2-oidc';
import { JwksValidationHandler } from 'angular-oauth2-oidc-jwks';
import { BehaviorSubject } from 'rxjs';
import { asapScheduler, BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root',
@@ -14,20 +15,29 @@ export class AuthService {
return this._initialized.asObservable();
}
constructor(private _config: Config, private readonly _oAuthService: OAuthService) {}
private _authConfig: AuthConfig;
constructor(private _config: Config, private readonly _oAuthService: OAuthService) {
this._oAuthService.events?.subscribe((event) => {
if (event.type === 'token_received') {
console.log('SSO Token Expiration:', new Date(this._oAuthService.getAccessTokenExpiration()));
}
});
}
async init() {
if (this._initialized.getValue()) {
throw new Error('AuthService is already initialized');
}
const authConfig: AuthConfig = this._config.get('@core/auth');
this._authConfig = this._config.get('@core/auth');
authConfig.redirectUri = window.location.origin;
authConfig.silentRefreshRedirectUri = window.location.origin + '/silent-refresh.html';
authConfig.useSilentRefresh = true;
this._authConfig.redirectUri = window.location.origin;
this._oAuthService.configure(authConfig);
this._authConfig.silentRefreshRedirectUri = window.location.origin + '/silent-refresh.html';
this._authConfig.useSilentRefresh = true;
this._oAuthService.configure(this._authConfig);
this._oAuthService.tokenValidationHandler = new JwksValidationHandler();
this._oAuthService.setupAutomaticSilentRefresh();
@@ -84,5 +94,32 @@ export class AuthService {
async logout() {
await this._oAuthService.revokeTokenAndLogout();
// asapScheduler.schedule(() => {
// window.location.reload();
// }, 250);
}
hasRole(role: string | string[]) {
const roles = coerceArray(role);
const userRoles = this.getClaimByKey('role');
if (isNullOrUndefined(userRoles)) {
return false;
}
return roles.every((r) => userRoles.includes(r));
}
async refresh() {
try {
if (this._authConfig.responseType.includes('code') && this._authConfig.scope.includes('offline_access')) {
await this._oAuthService.refreshToken();
} else {
await this._oAuthService.silentRefresh();
}
} catch (error) {
console.error(error);
}
}
}

View File

@@ -0,0 +1,65 @@
// import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator';
// import { IfRoleDirective } from './if-role.directive';
// import { AuthService } from './auth.service';
// import { TemplateRef, ViewContainerRef } from '@angular/core';
// describe('IfRoleDirective', () => {
// let spectator: SpectatorDirective<IfRoleDirective>;
// const createDirective = createDirectiveFactory({
// directive: IfRoleDirective,
// mocks: [AuthService],
// });
// it('should create an instance', () => {
// spectator = createDirective(`<div *ifRole="'admin'"></div>`);
// expect(spectator.directive).toBeTruthy();
// });
// it('should render template when user has the role', () => {
// spectator = createDirective(`<div *ifRole="'admin'"></div>`);
// const authService = spectator.inject(AuthService);
// const viewContainerRef = spectator.inject(ViewContainerRef);
// const templateRef = spectator.inject(TemplateRef);
// authService.hasRole.and.returnValue(true);
// spectator.directive.ngOnChanges();
// expect(viewContainerRef.createEmbeddedView).toHaveBeenCalledWith(templateRef, spectator.directive.getContext());
// });
// it('should render else template when user does not have the role', () => {
// authService.hasRole.and.returnValue(false);
// const elseTemplateRef = {} as TemplateRef<any>;
// spectator = createDirective(`<ng-template #elseTemplateRef></ng-template><div *ifRole="'admin'; else elseTemplateRef"></div>`, {
// hostProps: {
// elseTemplateRef,
// },
// });
// spectator.directive.ngOnChanges();
// expect(viewContainerRef.createEmbeddedView).toHaveBeenCalledWith(elseTemplateRef, spectator.directive.getContext());
// });
// it('should render else template when user does not have the role using ifNotRole input', () => {
// authService.hasRole.and.returnValue(false);
// const elseTemplateRef = {} as TemplateRef<any>;
// spectator = createDirective(`<ng-template #elseTemplateRef></ng-template><div *ifNotRole="'admin'; else elseTemplateRef"></div>`, {
// hostProps: {
// elseTemplateRef,
// },
// });
// spectator.directive.ngOnChanges();
// expect(viewContainerRef.createEmbeddedView).toHaveBeenCalledWith(elseTemplateRef, spectator.directive.getContext());
// });
// it('should clear view when user does not have the role and elseTemplateRef is not defined', () => {
// authService.hasRole.and.returnValue(false);
// spectator = createDirective(`<div *ifRole="'admin'"></div>`);
// spectator.directive.ngOnChanges();
// expect(viewContainerRef.clear).toHaveBeenCalled();
// });
// it('should set $implicit to ifRole or ifNotRole input', () => {
// spectator = createDirective(`<div *ifRole="'admin'"></div>`);
// expect(spectator.directive.getContext().$implicit).toEqual('admin');
// spectator.setInput('ifNotRole', 'user');
// expect(spectator.directive.getContext().$implicit).toEqual('user');
// });
// });

View File

@@ -0,0 +1,59 @@
import { Directive, Input, OnChanges, TemplateRef, ViewContainerRef } from '@angular/core';
import { AuthService } from './auth.service';
@Directive({
selector: '[ifRole],[ifRoleElse],[ifNotRole],[ifNotRoleElse]',
})
export class IfRoleDirective implements OnChanges {
@Input()
ifRole: string | string[];
@Input()
ifRoleElse: TemplateRef<any>;
@Input()
ifNotRole: string | string[];
@Input()
ifNotRoleElse: TemplateRef<any>;
get renderTemplateRef() {
if (this.ifRole) {
return this._authService.hasRole(this.ifRole);
}
if (this.ifNotRole) {
return !this._authService.hasRole(this.ifNotRole);
}
return false;
}
get elseTemplateRef() {
return this.ifRoleElse || this.ifNotRoleElse;
}
constructor(private _templateRef: TemplateRef<any>, private _viewContainer: ViewContainerRef, private _authService: AuthService) {}
ngOnChanges() {
this.render();
}
render() {
if (this.renderTemplateRef) {
this._viewContainer.createEmbeddedView(this._templateRef, this.getContext());
return;
}
if (this.elseTemplateRef) {
this._viewContainer.createEmbeddedView(this.elseTemplateRef, this.getContext());
return;
}
this._viewContainer.clear();
}
getContext(): { $implicit: string | string[] } {
return {
$implicit: this.ifRole || this.ifNotRole,
};
}
}

View File

@@ -135,9 +135,9 @@ export class BreadcrumbService {
crumbs.forEach((crumb) => this.removeBreadcrumb(crumb.id));
}
getLatestBreadcrumbForSection(section: 'customer' | 'branch') {
getLatestBreadcrumbForSection(section: 'customer' | 'branch', predicate: (crumb: Breadcrumb) => boolean = (_) => true) {
return this.store
.select(selectors.selectBreadcrumbsBySection, { section })
.pipe(map((crumbs) => crumbs.sort((a, b) => b.changed - a.changed).find((f) => true)));
.pipe(map((crumbs) => crumbs.sort((a, b) => b.changed - a.changed).find((f) => predicate(f))));
}
}

View File

@@ -22,7 +22,7 @@ export interface Breadcrumb {
/**
* Url
*/
path: string;
path: string | any[];
/**
* Query Parameter

View File

@@ -1,21 +1,85 @@
import { Injectable } from '@angular/core';
import { Platform } from '@angular/cdk/platform';
import { NativeContainerService } from 'native-container';
import { BreakpointObserver } from '@angular/cdk/layout';
const MATCH_TABLET = '(max-width: 1024px)';
const MATCH_DESKTOP_SMALL = '(min-width: 1025px) and (max-width: 1439px)';
const MATCH_DESKTOP = '(min-width: 1280px)';
const MATCH_DESKTOP_LARGE = '(min-width: 1440px)';
const MATCH_DESKTOP_X_LARGE = '(min-width: 1920px)';
const MATCH_DESKTOP_XX_LARGE = '(min-width: 2736px)';
@Injectable({
providedIn: 'root',
})
export class EnvironmentService {
constructor(private _platform: Platform) {}
constructor(
private _platform: Platform,
private _nativeContainer: NativeContainerService,
private _breakpointObserver: BreakpointObserver
) {}
matchTablet(): boolean {
return this._breakpointObserver.isMatched(MATCH_TABLET);
}
matchTablet$ = this._breakpointObserver.observe(MATCH_TABLET);
matchDesktopSmall(): boolean {
return this._breakpointObserver.isMatched(MATCH_DESKTOP_SMALL);
}
matchDesktopSmall$ = this._breakpointObserver.observe(MATCH_DESKTOP_SMALL);
matchDesktop(): boolean {
return this._breakpointObserver.isMatched(MATCH_DESKTOP);
}
matchDesktop$ = this._breakpointObserver.observe(MATCH_DESKTOP);
matchDesktopLarge(): boolean {
return this._breakpointObserver.isMatched(MATCH_DESKTOP_LARGE);
}
matchDesktopLarge$ = this._breakpointObserver.observe(MATCH_DESKTOP_LARGE);
matchDesktopXLarge(): boolean {
return this._breakpointObserver.isMatched(MATCH_DESKTOP_X_LARGE);
}
matchDesktopXLarge$ = this._breakpointObserver.observe(MATCH_DESKTOP_X_LARGE);
matchDesktopXXLarge(): boolean {
return this._breakpointObserver.isMatched(MATCH_DESKTOP_XX_LARGE);
}
matchDesktopXXLarge$ = this._breakpointObserver.observe(MATCH_DESKTOP_XX_LARGE);
/**
* @deprecated Use `matchDesktopSmall` or 'matchDesktop' instead.
*/
isDesktop(): boolean {
return !this.isTablet();
}
/**
* @deprecated Use `matchTablet` instead.
*/
isTablet(): boolean {
return this._platform.ANDROID || this._platform.IOS;
return this.isNative() || this.isSafari();
}
isNative(): boolean {
return this._nativeContainer.isNative;
}
isSafari(): boolean {
return this._platform.SAFARI;
return this._platform.IOS && this._platform.SAFARI;
}
}

View File

@@ -34,6 +34,11 @@ export class DomainAvailabilityService {
private _branchService: StoreCheckoutBranchService
) {}
@memorize({ ttl: 10000 })
memorizedAvailabilityShippingAvailability(request: Array<AvailabilityRequestDTO>) {
return this._availabilityService.AvailabilityShippingAvailability(request).pipe(shareReplay(1));
}
@memorize()
getSuppliers(): Observable<SupplierDTO[]> {
return this._supplierService.StoreCheckoutSupplierGetSuppliers({}).pipe(
@@ -59,8 +64,8 @@ export class DomainAvailabilityService {
}
@memorize()
getStockByBranch(branch: BranchDTO): Observable<StockDTO> {
return this._stockService.StockGetStocksByBranch({ branchId: branch.id }).pipe(
getStockByBranch(branchId: number): Observable<StockDTO> {
return this._stockService.StockGetStocksByBranch({ branchId }).pipe(
map((response) => response.result),
map((result) => result?.find((_) => true)),
shareReplay(1)
@@ -149,7 +154,7 @@ export class DomainAvailabilityService {
quantity: number;
branch?: BranchDTO;
}): Observable<AvailabilityDTO> {
const request = !!branch ? this.getStockByBranch(branch) : this.getDefaultStock();
const request = !!branch ? this.getStockByBranch(branch.id) : this.getDefaultStock();
return request.pipe(
switchMap((s) =>
combineLatest([
@@ -160,7 +165,7 @@ export class DomainAvailabilityService {
),
map(([response, supplier, defaultBranch]) => {
const price = item?.price;
return this._mapToTakeAwayAvailability({ response, supplier, branch: branch ?? defaultBranch, quantity, price });
return this._mapToTakeAwayAvailability({ response, supplier, branchId: branch.id ?? defaultBranch.id, quantity, price });
}),
shareReplay(1)
);
@@ -183,7 +188,7 @@ export class DomainAvailabilityService {
this.getTakeAwaySupplier(),
]).pipe(
map(([response, supplier]) => {
return this._mapToTakeAwayAvailability({ response, supplier, branch, quantity, price });
return this._mapToTakeAwayAvailability({ response, supplier, branchId: branch.id, quantity, price });
}),
shareReplay(1)
);
@@ -193,16 +198,19 @@ export class DomainAvailabilityService {
eans,
price,
quantity,
branchId,
}: {
eans: string[];
price: PriceDTO;
quantity: number;
branchId?: number;
}): Observable<AvailabilityDTO> {
return this.getDefaultStock().pipe(
const request = !!branchId ? this.getStockByBranch(branchId) : this.getDefaultStock();
return request.pipe(
switchMap((s) => this._stockService.StockInStockByEAN({ eans, stockId: s.id })),
withLatestFrom(this.getTakeAwaySupplier(), this.getDefaultBranch()),
map(([response, supplier, branch]) => {
return this._mapToTakeAwayAvailability({ response, supplier, branch, quantity, price });
map(([response, supplier, defaultBranch]) => {
return this._mapToTakeAwayAvailability({ response, supplier, branchId: branchId ?? defaultBranch.id, quantity, price });
}),
shareReplay(1)
);
@@ -246,57 +254,53 @@ export class DomainAvailabilityService {
@memorize({ ttl: 10000 })
getDeliveryAvailability({ item, quantity }: { item: ItemData; quantity: number }): Observable<AvailabilityDTO> {
return this._availabilityService
.AvailabilityShippingAvailability([
{
ean: item?.ean,
itemId: item?.itemId ? String(item?.itemId) : null,
price: item?.price,
qty: quantity,
},
])
.pipe(
timeout(5000),
map((r) => this._mapToShippingAvailability(r.result)?.find((_) => true)),
shareReplay(1)
);
return this.memorizedAvailabilityShippingAvailability([
{
ean: item?.ean,
itemId: item?.itemId ? String(item?.itemId) : null,
price: item?.price,
qty: quantity,
},
]).pipe(
timeout(5000),
map((r) => this._mapToShippingAvailability(r.result)?.find((_) => true)),
shareReplay(1)
);
}
@memorize({ ttl: 10000 })
getDigDeliveryAvailability({ item, quantity }: { item: ItemData; quantity: number }): Observable<AvailabilityDTO> {
return this._availabilityService
.AvailabilityShippingAvailability([
{
qty: quantity,
ean: item?.ean,
itemId: item?.itemId ? String(item?.itemId) : null,
price: item?.price,
},
])
.pipe(
timeout(5000),
map((r) => {
const availabilities = r.result;
const preferred = availabilities?.find((f) => f.preferred === 1);
return this.memorizedAvailabilityShippingAvailability([
{
qty: quantity,
ean: item?.ean,
itemId: item?.itemId ? String(item?.itemId) : null,
price: item?.price,
},
]).pipe(
timeout(5000),
map((r) => {
const availabilities = r.result;
const preferred = availabilities?.find((f) => f.preferred === 1);
const availability: AvailabilityDTO = {
availabilityType: preferred?.status,
ssc: preferred?.ssc,
sscText: preferred?.sscText,
supplier: { id: preferred?.supplierId },
isPrebooked: preferred?.isPrebooked,
estimatedShippingDate: preferred?.requestStatusCode === '32' ? preferred?.altAt : preferred?.at,
estimatedDelivery: preferred?.estimatedDelivery,
price: preferred?.price,
logistician: { id: preferred?.logisticianId },
supplierProductNumber: preferred?.supplierProductNumber,
supplierInfo: preferred?.requestStatusCode,
lastRequest: preferred?.requested,
};
return availability;
}),
shareReplay(1)
);
const availability: AvailabilityDTO = {
availabilityType: preferred?.status,
ssc: preferred?.ssc,
sscText: preferred?.sscText,
supplier: { id: preferred?.supplierId },
isPrebooked: preferred?.isPrebooked,
estimatedShippingDate: preferred?.requestStatusCode === '32' ? preferred?.altAt : preferred?.at,
estimatedDelivery: preferred?.estimatedDelivery,
price: preferred?.price,
logistician: { id: preferred?.logisticianId },
supplierProductNumber: preferred?.supplierProductNumber,
supplierInfo: preferred?.requestStatusCode,
lastRequest: preferred?.requested,
};
return availability;
}),
shareReplay(1)
);
}
@memorize({ ttl: 10000 })
@@ -330,37 +334,35 @@ export class DomainAvailabilityService {
@memorize({ ttl: 10000 })
getDownloadAvailability({ item }: { item: ItemData }): Observable<AvailabilityDTO> {
return this._availabilityService
.AvailabilityShippingAvailability([
{
ean: item?.ean,
itemId: item?.itemId ? String(item?.itemId) : null,
price: item?.price,
qty: 1,
},
])
.pipe(
map((r) => {
const availabilities = r.result;
const preferred = availabilities?.find((f) => f.preferred === 1);
return this.memorizedAvailabilityShippingAvailability([
{
ean: item?.ean,
itemId: item?.itemId ? String(item?.itemId) : null,
price: item?.price,
qty: 1,
},
]).pipe(
map((r) => {
const availabilities = r.result;
const preferred = availabilities?.find((f) => f.preferred === 1);
const availability: AvailabilityDTO = {
availabilityType: preferred?.status,
ssc: preferred?.ssc,
sscText: preferred?.sscText,
supplier: { id: preferred?.supplierId },
isPrebooked: preferred?.isPrebooked,
estimatedShippingDate: preferred?.requestStatusCode === '32' ? preferred?.altAt : preferred?.at,
price: preferred?.price,
supplierProductNumber: preferred?.supplierProductNumber,
logistician: { id: preferred?.logisticianId },
supplierInfo: preferred?.requestStatusCode,
lastRequest: preferred?.requested,
};
return availability;
}),
shareReplay(1)
);
const availability: AvailabilityDTO = {
availabilityType: preferred?.status,
ssc: preferred?.ssc,
sscText: preferred?.sscText,
supplier: { id: preferred?.supplierId },
isPrebooked: preferred?.isPrebooked,
estimatedShippingDate: preferred?.requestStatusCode === '32' ? preferred?.altAt : preferred?.at,
price: preferred?.price,
supplierProductNumber: preferred?.supplierProductNumber,
logistician: { id: preferred?.logisticianId },
supplierInfo: preferred?.requestStatusCode,
lastRequest: preferred?.requested,
};
return availability;
}),
shareReplay(1)
);
}
@memorize({ ttl: 10000 })
@@ -398,7 +400,7 @@ export class DomainAvailabilityService {
@memorize({ ttl: 10000 })
getDeliveryAvailabilities(payload: AvailabilityRequestDTO[]) {
return this._availabilityService.AvailabilityShippingAvailability(payload).pipe(
return this.memorizedAvailabilityShippingAvailability(payload).pipe(
timeout(20000),
map((response) => this._mapToShippingAvailability(response.result))
);
@@ -406,7 +408,7 @@ export class DomainAvailabilityService {
@memorize({ ttl: 10000 })
getDigDeliveryAvailabilities(payload: AvailabilityRequestDTO[]) {
return this._availabilityService.AvailabilityShippingAvailability(payload).pipe(
return this.memorizedAvailabilityShippingAvailability(payload).pipe(
timeout(20000),
map((response) => this._mapToShippingAvailability(response.result))
);
@@ -444,6 +446,9 @@ export class DomainAvailabilityService {
}
isAvailable({ availability }: { availability: AvailabilityDTO }) {
if (availability?.supplier?.id === 16 && availability?.inStock == 0) {
return false;
}
return [2, 32, 256, 1024, 2048, 4096].some((code) => availability?.availabilityType === code);
}
@@ -476,17 +481,17 @@ export class DomainAvailabilityService {
private _mapToTakeAwayAvailability({
response,
supplier,
branch,
branchId,
quantity,
price,
}: {
response: ResponseArgsOfIEnumerableOfStockInfoDTO;
supplier: SupplierDTO;
branch: BranchDTO;
branchId: number;
quantity: number;
price: PriceDTO;
}): AvailabilityDTO {
const stockInfo = response.result?.find((si) => si.branchId === branch.id);
const stockInfo = response.result?.find((si) => si.branchId === branchId);
const inStock = stockInfo?.inStock ?? 0;
const availability: AvailabilityDTO = {
availabilityType: quantity <= inStock ? 1024 : 1, // 1024 (=Available)
@@ -609,7 +614,7 @@ export class DomainAvailabilityService {
}
getInStock({ itemIds, branchId }: { itemIds: number[]; branchId: number }): Observable<StockInfoDTO[]> {
return this.getStockByBranch({ id: branchId }).pipe(
return this.getStockByBranch(branchId).pipe(
mergeMap((stock) =>
this._stockService.StockInStock({ articleIds: itemIds, stockId: stock.id }).pipe(map((response) => response.result))
)

View File

@@ -1,8 +1,8 @@
import { Injectable } from '@angular/core';
import { StockInfoDTO } from '@swagger/remi';
import { groupBy } from 'lodash';
import { BehaviorSubject, combineLatest, Observable, OperatorFunction, Subject, timer } from 'rxjs';
import { buffer, bufferTime, bufferWhen, debounceTime } from 'rxjs/operators';
import { groupBy, isEqual, uniqWith } from 'lodash';
import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs';
import { buffer, debounceTime, distinctUntilChanged } from 'rxjs/operators';
import { DomainAvailabilityService } from './availability.service';
export type ItemBranch = { itemId: number; branchId: number };
@@ -25,8 +25,9 @@ export class DomainInStockService {
}
private _handleStockDataToFetch = (itemBranchData: ItemBranch[]) => {
if (itemBranchData?.length > 0) {
this._fetchStockData(itemBranchData);
const unique = uniqWith(itemBranchData, isEqual);
if (unique?.length > 0) {
this._fetchStockData(unique);
}
};
@@ -44,16 +45,24 @@ export class DomainInStockService {
const key = this.getKey({ itemId, branchId });
this._addToInStockQueue({ itemId, branchId });
const sub = combineLatest([this._inStockMap, this._inStockFetchingMap]).subscribe(([inStockMap, inStockFetchingMap]) => {
const inStock: InStock = {
itemId,
branchId,
inStock: inStockMap[key],
fetching: inStockFetchingMap[key] ?? false,
};
let _previousValue: InStock;
obs.next(inStock);
});
const sub = combineLatest([this._inStockMap, this._inStockFetchingMap])
.pipe(distinctUntilChanged(isEqual))
.subscribe(([inStockMap, inStockFetchingMap]) => {
const inStock: InStock = {
itemId,
branchId,
inStock: inStockMap[key],
fetching: inStockFetchingMap[key] ?? false,
};
if (!isEqual(inStock, _previousValue)) {
obs.next(inStock);
}
_previousValue = inStock;
});
return () => {
sub.unsubscribe();
};
@@ -71,10 +80,10 @@ export class DomainInStockService {
this._inStockFetchingMap.next({ ...current, [key]: value });
}
private _setInStock({ itemId, branchId }: ItemBranch, value: number) {
private _setInStock({ itemId, branchId }: ItemBranch, inStock: number) {
const key = this.getKey({ itemId, branchId });
const current = this._inStockMap.getValue();
this._inStockMap.next({ ...current, [key]: value });
this._inStockMap.next({ ...current, [key]: inStock });
}
private _fetchStockData(itemBranchData: ItemBranch[]) {
@@ -92,9 +101,11 @@ export class DomainInStockService {
itemIds.forEach((itemId) => {
const stockInfo = stockInfos.find((stockInfo) => stockInfo.itemId === itemId && stockInfo.branchId === branchId);
let inStock = 0;
if (stockInfo?.inStock) {
inStock = stockInfo.inStock;
}
this._setInStockFetching({ itemId, branchId }, false);
this._setInStock({ itemId, branchId }, inStock);
});

View File

@@ -26,6 +26,7 @@ import {
StoreCheckoutBuyerService,
StoreCheckoutPayerService,
StoreCheckoutBranchService,
ItemsResult,
} from '@swagger/checkout';
import { DisplayOrderDTO, DisplayOrderItemDTO, OrderCheckoutService, ReorderValues } from '@swagger/oms';
import { isNullOrUndefined, memorize } from '@utils/common';
@@ -198,7 +199,15 @@ export class DomainCheckoutService {
);
}
canAddItems({ processId, payload, orderType }: { processId: number; payload: ItemPayload[]; orderType: string }) {
canAddItems({
processId,
payload,
orderType,
}: {
processId: number;
payload: ItemPayload[];
orderType: string;
}): Observable<ItemsResult[]> {
return this.getShoppingCart({ processId }).pipe(
first(),
withLatestFrom(this.store.select(DomainCheckoutSelectors.selectCustomerFeaturesByProcessId, { processId })),
@@ -217,7 +226,8 @@ export class DomainCheckoutService {
})
.pipe(
map((response) => {
return response.result;
// TODO: remove this when the API is fixed
return (response.result as unknown) as ItemsResult[];
})
);
})
@@ -918,7 +928,7 @@ export class DomainCheckoutService {
//#region Common
private updateProcessCount(processId: number, count: number) {
this.applicationService.patchProcess(processId, { data: { count } });
this.applicationService.patchProcessData(processId, { count });
}
//#endregion

View File

@@ -22,7 +22,7 @@ export class PrintCompartmentLabelActionHandler extends ActionHandler<OrderItems
content: PrintModalComponent,
config: { showScrollbarY: false },
data: {
printImmediately: !this.nativeContainerService.isUiWebview().isNative,
printImmediately: !this.nativeContainerService.isNative,
printerType: 'Label',
print: (printer) =>
this.domainPrinterService

View File

@@ -23,7 +23,7 @@ export class PrintShippingNoteActionHandler extends ActionHandler<OrderItemsCont
content: PrintModalComponent,
config: { showScrollbarY: false },
data: {
printImmediately: !this.nativeContainerService.isUiWebview().isNative,
printImmediately: !this.nativeContainerService.isNative,
printerType: 'Label',
print: async (printer) => {
try {

View File

@@ -0,0 +1,29 @@
import { Injectable } from '@angular/core';
import { AutocompleteTokenDTO, OrderService, QueryTokenDTO } from '@swagger/oms';
@Injectable({ providedIn: 'root' })
export class DomainCustomerOrderService {
constructor(private _orderService: OrderService) {}
complete(payload: AutocompleteTokenDTO) {
return this._orderService.OrderKundenbestellungenAutocomplete(payload);
}
search(payload: QueryTokenDTO) {
return this._orderService.OrderKundenbestellungen({ ...payload });
// branch_id'
}
getOrderItemsByOrderNumber(orderNumber: string) {
return this._orderService.OrderKundenbestellungen({
filter: { all_branches: 'true', archive: 'true' },
input: {
qs: orderNumber,
},
});
}
settings() {
return this._orderService.OrderKundenbestellungenSettings();
}
}

View File

@@ -1,5 +1,6 @@
import { Injectable } from '@angular/core';
import {
AutocompleteTokenDTO,
BranchService,
ChangeStockStatusCodeValues,
HistoryDTO,
@@ -10,6 +11,7 @@ import {
OrderItemSubsetDTO,
OrderListItemDTO,
OrderService,
QueryTokenDTO,
ReceiptService,
ResponseArgsOfIEnumerableOfHistoryDTO,
StatusValues,

View File

@@ -1,5 +1,7 @@
import { Injectable } from '@angular/core';
import { ReceiptOrderItemSubsetReferenceValues, ReceiptService } from '@swagger/oms';
import { memorize } from '@utils/common';
import { shareReplay } from 'rxjs/operators';
@Injectable()
export class DomainReceiptService {
@@ -9,9 +11,12 @@ export class DomainReceiptService {
return this.receiptService.ReceiptCreateShippingNote2(params);
}
@memorize({ ttl: 1000 })
getReceipts(payload: ReceiptOrderItemSubsetReferenceValues) {
return this.receiptService.ReceiptGetReceiptsByOrderItemSubset({
payload: payload,
});
return this.receiptService
.ReceiptGetReceiptsByOrderItemSubset({
payload: payload,
})
.pipe(shareReplay(1));
}
}

View File

@@ -7,3 +7,4 @@ export * from './lib/receipt.service';
export * from './lib/oms.service';
export * from './lib/oms.module';
export * from './lib/action-handlers';
export * from './lib/customer-order.service';

View File

@@ -5,7 +5,6 @@ import {
CheckoutPrintService,
ItemDTO,
OMSPrintService,
PriceQRCodeDTO,
PrintRequestOfIEnumerableOfItemDTO,
PrintRequestOfIEnumerableOfLong,
PrintRequestOfIEnumerableOfPriceQRCodeDTO,
@@ -13,6 +12,12 @@ import {
ResponseArgs,
LoyaltyCardPrintService,
} from '@swagger/print';
import {
DocumentPayloadOfIEnumerableOfProductListItemDTO,
ProductListItemDTO,
ProductListService,
ResponseArgsOfString,
} from '@swagger/wws';
import { Observable, of } from 'rxjs';
import { catchError, filter, map, switchMap, timeout } from 'rxjs/operators';
import { Printer } from './defs/printer.model';
@@ -27,7 +32,8 @@ export class DomainPrinterService {
private catalogPrintService: CatalogPrintService,
private checkoutPrintService: CheckoutPrintService,
private eisPublicDocumentService: EISPublicDocumentService,
private _loyaltyCardPrintService: LoyaltyCardPrintService
private _loyaltyCardPrintService: LoyaltyCardPrintService,
private _productListService: ProductListService
) {}
getAvailablePrinters(): Observable<Printer[] | { error: string }> {
@@ -202,6 +208,29 @@ export class DomainPrinterService {
});
}
printProductListItemsResponse(payload: DocumentPayloadOfIEnumerableOfProductListItemDTO): Observable<ResponseArgsOfString> {
return this._productListService.ProductListProductListItemPdfAsBase64(payload);
}
printProductListItems({
data,
printer,
title,
}: {
data: ProductListItemDTO[];
printer: string;
title?: string;
}): Observable<ResponseArgs> {
return this.printProductListItemsResponse({ data, title }).pipe(
switchMap((res) => {
if (!res.error) {
return this.printPdf({ printer, data: res.result });
}
return of(res);
})
);
}
printDisplayInfoDTOArticles({
articles,
printer,

View File

@@ -0,0 +1,25 @@
# ProductList
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 15.0.0.
## Code scaffolding
Run `ng generate component component-name --project product-list` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project product-list`.
> Note: Don't forget to add `--project product-list` or else it will be added to the default project in your `angular.json` file.
## Build
Run `ng build product-list` to build the project. The build artifacts will be stored in the `dist/` directory.
## Publishing
After building your library with `ng build product-list`, go to the dist folder `cd dist/product-list` and run `npm publish`.
## Running unit tests
Run `ng test product-list` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.

View File

@@ -1,6 +1,6 @@
{
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../../dist/shell/breadcrumb",
"dest": "../../../dist/domain/product-list",
"lib": {
"entryFile": "src/public-api.ts"
}

View File

@@ -1,9 +1,9 @@
{
"name": "@shell/process",
"name": "@domain/product-list",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^12.2.0",
"@angular/core": "^12.2.0"
"@angular/common": "^15.0.0",
"@angular/core": "^15.0.0"
},
"dependencies": {
"tslib": "^2.3.0"

View File

@@ -0,0 +1,4 @@
import { NgModule } from '@angular/core';
@NgModule({})
export class ProductListModule {}

View File

@@ -0,0 +1,52 @@
import { Injectable } from '@angular/core';
import {
BatchResponseArgsOfProductListItemDTOAndString,
ListResponseArgsOfProductListItemDTO,
ProductListItemDTO,
ProductListService,
QuerySettingsDTO,
QueryTokenDTO,
ResponseArgsOfProductListItemDTO,
ResponseArgsOfQuerySettingsDTO,
} from '@swagger/wws';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class DomainProductListService {
constructor(private _productList: ProductListService) {}
getQuerySettingsResponse(): Observable<ResponseArgsOfQuerySettingsDTO> {
return this._productList.ProductListQueryProductListItemsSettings();
}
getQuerySettings(): Observable<QuerySettingsDTO> {
return this.getQuerySettingsResponse().pipe(map((response) => response.result));
}
queryProductListResponse(queryToken: QueryTokenDTO): Observable<ListResponseArgsOfProductListItemDTO> {
return this._productList.ProductListQueryProductListItem(queryToken);
}
queryProductList(queryToken: QueryTokenDTO): Observable<ProductListItemDTO[]> {
return this.queryProductListResponse(queryToken).pipe(map((response) => response.result));
}
completeProductListItemResponse(productListItemUId: string): Observable<ResponseArgsOfProductListItemDTO> {
return this._productList.ProductListProductListItemCompleted(productListItemUId);
}
completeProductListItem(productListItemUId: string): Observable<ProductListItemDTO> {
return this.completeProductListItemResponse(productListItemUId).pipe(map((response) => response.result));
}
completeProductListItemsResponse(uids: string[]): Observable<BatchResponseArgsOfProductListItemDTOAndString> {
return this._productList.ProductListProductListItemsCompleted(uids);
}
completeProductListItems(uids: string[]): Observable<BatchResponseArgsOfProductListItemDTOAndString> {
return this.completeProductListItemsResponse(uids);
}
}

View File

@@ -0,0 +1,6 @@
/*
* Public API Surface of product-list
*/
export * from './lib/product-list.service';
export * from './lib/product-list.module';

View File

@@ -6,14 +6,9 @@
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": [],
"lib": [
"dom",
"es2018"
]
"types": []
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

View File

@@ -11,4 +11,4 @@
"**/*.spec.ts",
"**/*.d.ts"
]
}
}

View File

@@ -447,7 +447,7 @@ export class DomainRemissionService {
* Create a new receipt for the given return/remission
* @param returnId Return ID
* @param receiptNumber Receipt number
* @returns ReturnDTO - ShippingDocument
* @returns ReceiptDTO
*/
async createReceipt(returnDTO: ReturnDTO, receiptNumber?: string): Promise<ReceiptDTO> {
const stock = await this._getStock();
@@ -471,14 +471,41 @@ export class DomainRemissionService {
return receipt;
}
async completeReceipt(returnId: number, receiptId: number, packageCode: string): Promise<ReceiptDTO> {
/**
* Create a new Package and assign it to a receipt
* @param returnId Return ID
* @param receiptId Receipt ID
* @param packageNumber Packagenumber
* @returns ReceiptDTO
*/
async createReceiptAndAssignPackage({
returnId,
receiptId,
packageNumber,
}: {
returnId: number;
receiptId: number;
packageNumber: string;
}): Promise<ReceiptDTO> {
const response = await this._returnService
.ReturnCreateAndAssignPackage({
returnId,
receiptId,
data: {
packageNumber,
},
})
.toPromise();
const receipt: ReceiptDTO = response.result;
return receipt;
}
async completeReceipt(returnId: number, receiptId: number): Promise<ReceiptDTO> {
const res = await this._returnService
.ReturnFinalizeReceipt({
returnId,
receiptId,
data: {
packageCode,
},
data: {},
})
.toPromise();

View File

@@ -1,4 +1,5 @@
import { Inject, Injectable, InjectionToken } from '@angular/core';
import { AuthService } from '@core/auth';
import { SignalrHub, SignalRHubOptions } from '@core/signalr';
import { BehaviorSubject, merge, of } from 'rxjs';
import { filter, map, shareReplay, tap, withLatestFrom } from 'rxjs/operators';
@@ -9,7 +10,16 @@ export const NOTIFICATIONS_HUB_OPTIONS = new InjectionToken<SignalRHubOptions>('
@Injectable()
export class NotificationsHub extends SignalrHub {
updateNotification$ = new BehaviorSubject<MessageBoardItemDTO>(undefined);
constructor(@Inject(NOTIFICATIONS_HUB_OPTIONS) options: SignalRHubOptions) {
get branchNo() {
return String(this._auth.getClaimByKey('branch_no') || this._auth.getClaimByKey('sub'));
}
get sessionStoragesessionStorageKey() {
return `NOTIFICATIONS_BOARD_${this.branchNo}`;
}
constructor(@Inject(NOTIFICATIONS_HUB_OPTIONS) options: SignalRHubOptions, private _auth: AuthService) {
super(options);
}
@@ -31,12 +41,12 @@ export class NotificationsHub extends SignalrHub {
private _storeNotifactions(data: EnvelopeDTO<MessageBoardItemDTO[]>) {
if (data) {
localStorage.setItem('NOTIFICATIONS_BOARD', JSON.stringify(data));
sessionStorage.setItem(this.sessionStoragesessionStorageKey, JSON.stringify(data));
}
}
private _getNotifications(): EnvelopeDTO<MessageBoardItemDTO[]> {
const stringData = localStorage.getItem('NOTIFICATIONS_BOARD');
const stringData = sessionStorage.getItem(this.sessionStoragesessionStorageKey);
if (stringData) {
return JSON.parse(stringData);
}

View File

@@ -14,10 +14,11 @@ import {
CanActivateTaskCalendarGuard,
IsAuthenticatedGuard,
} from './guards';
import { CanActivateAssortmentGuard } from './guards/can-activate-assortment.guard';
import { CanActivatePackageInspectionGuard } from './guards/can-activate-package-inspection.guard';
import { MainComponent } from './main.component';
import { PreviewComponent } from './preview';
import { BranchSectionResolver, CustomerSectionResolver, ProcessIdResolver } from './resolvers';
import { ShellComponent, ShellModule } from './shell';
import { TokenLoginComponent, TokenLoginModule } from './token-login';
const routes: Routes = [
@@ -38,7 +39,7 @@ const routes: Routes = [
children: [
{
path: 'kunde',
component: ShellComponent,
component: MainComponent,
children: [
{
path: 'dashboard',
@@ -55,6 +56,17 @@ const routes: Routes = [
canActivate: [CanActivateProductWithProcessIdGuard],
resolve: { processId: ProcessIdResolver },
},
{
path: 'order',
loadChildren: () => import('@page/customer-order').then((m) => m.CustomerOrderModule),
canActivate: [CanActivateGoodsOutGuard],
},
{
path: ':processId/order',
loadChildren: () => import('@page/customer-order').then((m) => m.CustomerOrderModule),
canActivate: [CanActivateGoodsOutWithProcessIdGuard],
resolve: { processId: ProcessIdResolver },
},
{
path: 'customer',
loadChildren: () => import('@page/customer').then((m) => m.PageCustomerModule),
@@ -93,7 +105,7 @@ const routes: Routes = [
},
{
path: 'filiale',
component: ShellComponent,
component: MainComponent,
children: [
{
path: 'task-calendar',
@@ -115,6 +127,11 @@ const routes: Routes = [
loadChildren: () => import('@page/package-inspection').then((m) => m.PackageInspectionModule),
canActivate: [CanActivatePackageInspectionGuard],
},
{
path: 'assortment',
loadChildren: () => import('@page/assortment').then((m) => m.AssortmentModule),
canActivate: [CanActivateAssortmentGuard],
},
{ path: '**', redirectTo: 'task-calendar', pathMatch: 'full' },
],
resolve: { section: BranchSectionResolver },
@@ -134,7 +151,7 @@ if (isDevMode()) {
}
@NgModule({
imports: [RouterModule.forRoot(routes), ShellModule, TokenLoginModule],
imports: [RouterModule.forRoot(routes), TokenLoginModule],
exports: [RouterModule],
})
export class AppRoutingModule {}

View File

@@ -1,7 +1,3 @@
:host {
@apply block box-border;
}
button {
@apply fixed bottom-4 right-2 bg-blue-500 text-white font-bold py-2 px-4 rounded z-tooltip;
@apply block;
}

View File

@@ -9,6 +9,8 @@ import { CommonModule } from '@angular/common';
import { SwUpdate } from '@angular/service-worker';
import { NotificationsHub } from '@hub/notifications';
import { UserStateService } from '@swagger/isa';
import { UiModalService } from '@ui/modal';
import { AuthService } from '@core/auth';
describe('AppComponent', () => {
let spectator: Spectator<AppComponent>;
@@ -21,7 +23,7 @@ describe('AppComponent', () => {
component: AppComponent,
imports: [CommonModule, RouterTestingModule],
providers: [],
mocks: [Config, SwUpdate, UserStateService],
mocks: [Config, SwUpdate, UserStateService, UiModalService, AuthService],
});
beforeEach(() => {

View File

@@ -1,17 +1,18 @@
import { DOCUMENT } from '@angular/common';
import { Component, Inject, OnInit, Renderer2 } from '@angular/core';
import { Component, HostListener, Inject, OnInit, Renderer2 } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { SwUpdate, UpdateAvailableEvent } from '@angular/service-worker';
import { ApplicationService } from '@core/application';
import { Config } from '@core/config';
import { NotificationsHub } from '@hub/notifications';
import packageInfo from 'package';
import { interval, Observable, Subscription } from 'rxjs';
import { tap } from 'rxjs/operators';
import { Platform } from '@angular/cdk/platform';
import { Router } from '@angular/router';
import { asapScheduler, interval, Observable, Subscription } from 'rxjs';
import { UserStateService } from '@swagger/isa';
import { IsaLogProvider } from './providers';
import { EnvironmentService } from '@core/environment';
import { AuthService } from '@core/auth';
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
import { tap } from 'rxjs/operators';
@Component({
selector: 'app-root',
@@ -41,19 +42,34 @@ export class AppComponent implements OnInit {
private readonly _renderer: Renderer2,
private readonly _swUpdate: SwUpdate,
private readonly _notifications: NotificationsHub,
private readonly _platform: Platform,
private router: Router,
private infoService: UserStateService
private infoService: UserStateService,
private readonly _environment: EnvironmentService,
private readonly _authService: AuthService,
private readonly _modal: UiModalService
) {
this.updateClient();
IsaLogProvider.InfoService = infoService;
IsaLogProvider.InfoService = this.infoService;
}
ngOnInit() {
this.setTitle();
this.logVersion();
this.determinePlatform();
asapScheduler.schedule(() => this.determinePlatform(), 250);
this._appService.getSection$().subscribe(this.sectionChangeHandler.bind(this));
this.setupSilentRefresh();
}
// Setup interval for silent refresh
setupSilentRefresh() {
const silentRefreshInterval = this._config.get('silentRefresh.interval');
if (silentRefreshInterval > 0) {
interval(silentRefreshInterval).subscribe(() => {
if (this._authService.isAuthenticated()) {
this._authService.refresh();
}
});
}
}
setTitle() {
@@ -65,13 +81,15 @@ export class AppComponent implements OnInit {
}
determinePlatform() {
if (this._platform.IOS && !this._platform.SAFARI) {
this._renderer.addClass(this._document.body, 'tablet');
if (this._environment.isNative()) {
this._renderer.addClass(this._document.body, 'tablet-native');
} else if (this._platform.IOS && this._platform.SAFARI) {
this._renderer.addClass(this._document.body, 'tablet');
} else if (this._environment.isTablet()) {
this._renderer.addClass(this._document.body, 'tablet-browser');
} else if (this._platform.isBrowser) {
}
if (this._environment.isTablet()) {
this._renderer.addClass(this._document.body, 'tablet');
}
if (this._environment.isDesktop()) {
this._renderer.addClass(this._document.body, 'desktop');
}
}
@@ -86,49 +104,6 @@ export class AppComponent implements OnInit {
}
}
// --------------------------------------------------------
// Implementation before Angular Version 13.x.x
// async updateClient() {
// if (!this._swUpdate.isEnabled) {
// return;
// }
// await this.initialCheckForUpdate();
// this.checkForUpdateInterval();
// }
// checkForUpdateInterval() {
// this.updateAvailableObs = this._swUpdate.available.pipe(
// tap((availableEvent) => {
// if (availableEvent?.current?.hash !== availableEvent?.available?.hash) {
// this._notifications.updateNotification();
// this.subscriptions.unsubscribe();
// }
// })
// );
// this.subscriptions.add(
// interval(this._checkForUpdates).subscribe(async () => {
// await this._swUpdate.checkForUpdate();
// })
// );
// }
// async initialCheckForUpdate() {
// this.updateAvailableObs = this._swUpdate.available.pipe(
// tap((availableEvent) => {
// if (availableEvent?.current?.hash !== availableEvent?.available?.hash) {
// location.reload();
// }
// })
// );
// this.subscriptions.add(this.updateAvailableObs.subscribe());
// await this._swUpdate.checkForUpdate();
// }
// --------------------------------------------------------
// Implementation for Angular Version 13.x.x
updateClient() {
if (!this._swUpdate.isEnabled) {
return;
@@ -155,4 +130,22 @@ export class AppComponent implements OnInit {
}
});
}
@HostListener('window:visibilitychange', ['$event'])
onVisibilityChange(event: Event) {
// refresh token when app is in background
if (this._document.hidden && this._authService.isAuthenticated()) {
this._authService.refresh();
} else if (!this._authService.isAuthenticated()) {
return this._modal
.open({
content: UiMessageModalComponent,
title: 'Sie sind nicht mehr angemeldet',
data: { message: 'Sie werden neu angemeldet' },
})
.afterClosed$.subscribe(() => {
this._authService.login();
});
}
}
}

View File

@@ -32,13 +32,22 @@ import { IsaErrorHandler } from './providers/isa.error-handler';
import { ScanAdapterModule, ScanAdapterService, ScanditScanAdapterModule } from '@adapter/scan';
import { RootStateService } from './store/root-state.service';
import * as Commands from './commands';
import { UiIconModule } from '@ui/icon';
import { UiIconModule, UI_ICON_CFG } from '@ui/icon';
import { PreviewComponent } from './preview';
import { NativeContainerService } from 'native-container';
import { ShellModule } from '@shared/shell';
import { MainComponent } from './main.component';
registerLocaleData(localeDe, localeDeExtra);
registerLocaleData(localeDe, 'de', localeDeExtra);
export function _appInitializerFactory(config: Config, auth: AuthService, injector: Injector, scanAdapter: ScanAdapterService) {
export function _appInitializerFactory(
config: Config,
auth: AuthService,
injector: Injector,
scanAdapter: ScanAdapterService,
nativeContainer: NativeContainerService
) {
return async () => {
const statusElement = document.querySelector('#init-status');
statusElement.innerHTML = 'Konfigurationen werden geladen...';
@@ -52,6 +61,10 @@ export function _appInitializerFactory(config: Config, auth: AuthService, inject
await state.init();
}
statusElement.innerHTML = 'Native Container wird initialisiert...';
await nativeContainer.init();
statusElement.innerHTML = 'Scanner wird initialisiert...';
await scanAdapter.init();
};
}
@@ -63,11 +76,12 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
}
@NgModule({
declarations: [AppComponent],
declarations: [AppComponent, MainComponent],
imports: [
BrowserModule,
BrowserAnimationsModule,
HttpClientModule,
ShellModule.forRoot(),
AppRoutingModule,
AppSwaggerModule,
AppDomainModule,
@@ -92,19 +106,14 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
ScanAdapterModule.forRoot(),
ScanditScanAdapterModule.forRoot(),
PlatformModule,
UiIconModule.forRoot({
aliases: [
{ alias: 'd-account', name: 'account' },
{ alias: 'd-no-account', name: 'package-variant-closed' },
],
}),
UiIconModule.forRoot(),
],
providers: [
{
provide: APP_INITIALIZER,
useFactory: _appInitializerFactory,
multi: true,
deps: [Config, AuthService, Injector, ScanAdapterService],
deps: [Config, AuthService, Injector, ScanAdapterService, NativeContainerService],
},
{
provide: NOTIFICATIONS_HUB_OPTIONS,
@@ -126,6 +135,11 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
useClass: IsaErrorHandler,
},
{ provide: LOCALE_ID, useValue: 'de-DE' },
{
provide: UI_ICON_CFG,
useFactory: (config: Config) => config.get('@ui/icon'),
deps: [Config],
},
],
bootstrap: [AppComponent],
})

View File

@@ -0,0 +1,11 @@
<div class="odd:bg-slate-200 grid grid-flow-col justify-start" *ngFor="let log of logs$ | async">
<div class="p-2 w-100 grow-0">
{{ log.timestamp | date }}
</div>
<div class="p-2 w-50 grow-0">
{{ log.type }}
</div>
<div class="p-2 grow">
{{ log.args | json }}
</div>
</div>

View File

@@ -0,0 +1,6 @@
:host {
@apply block;
}
:host {
@apply grid grid-flow-row bg-white overflow-scroll;
}

View File

@@ -0,0 +1,17 @@
import { CommonModule } from '@angular/common';
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { DebugService } from './debug.service';
@Component({
selector: 'app-debug',
templateUrl: 'debug.component.html',
styleUrls: ['debug.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [CommonModule],
})
export class DebugComponent {
logs$ = this.debugService.logs$;
constructor(private debugService: DebugService) {}
}

View File

@@ -0,0 +1,25 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, fromEvent } from 'rxjs';
export interface ConsoleLog {
timestamp?: Date;
type: 'log' | 'warn' | 'error';
args: any[];
}
@Injectable()
export class DebugService {
private _consoleSubject = new BehaviorSubject<ConsoleLog[]>([]);
logs$ = this._consoleSubject.asObservable();
constructor() {
fromEvent(window, 'message').subscribe((event: MessageEvent) => {
this.add({ type: 'log', args: [event.data] });
});
}
add(log: ConsoleLog) {
this._consoleSubject.next([...this._consoleSubject.value, { ...log, timestamp: new Date() }]);
}
}

View File

@@ -0,0 +1,24 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { ApplicationService } from '@core/application';
import { Config } from '@core/config';
import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CanActivateAssortmentGuard implements CanActivate {
constructor(private readonly _applicationService: ApplicationService, private readonly _config: Config) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const process = await this._applicationService.getProcessById$(this._config.get('process.ids.assortment')).pipe(first()).toPromise();
if (!process) {
await this._applicationService.createProcess({
id: this._config.get('process.ids.assortment'),
type: 'assortment',
section: 'branch',
name: 'Sortiment',
});
}
this._applicationService.activateProcess(this._config.get('process.ids.assortment'));
return true;
}
}

View File

@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { DomainCheckoutService } from '@domain/checkout';
import { ProductCatalogNavigationService } from '@shared/services';
import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
@@ -9,6 +10,7 @@ export class CanActivateProductGuard implements CanActivate {
constructor(
private readonly _applicationService: ApplicationService,
private readonly _checkoutService: DomainCheckoutService,
private readonly _navigationService: ProductCatalogNavigationService,
private readonly _router: Router
) {}
@@ -38,7 +40,7 @@ export class CanActivateProductGuard implements CanActivate {
}
if (!lastActivatedProcessId) {
await this.fromCartProcess(processes, route);
await this.fromCartProcess(processes);
return false;
} else {
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(lastActivatedProcessId)]));
@@ -48,7 +50,7 @@ export class CanActivateProductGuard implements CanActivate {
}
// Bei offener Artikelsuche/Kundensuche und Klick auf Footer Artikelsuche
async fromCartProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot) {
async fromCartProcess(processes: ApplicationProcess[]) {
const newProcessId = Date.now();
await this._applicationService.createProcess({
id: newProcessId,
@@ -57,7 +59,7 @@ export class CanActivateProductGuard implements CanActivate {
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
});
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(newProcessId)]));
await this._navigationService.navigateToProductSearch({ processId: newProcessId });
}
// Bei offener Warenausgabe und Klick auf Footer Artikelsuche

View File

@@ -54,7 +54,7 @@ export class IsAuthenticatedGuard implements CanActivate {
const result = await this._scanService
.scan({
exclude: ['Dev', 'Native'],
exclude: ['Dev'],
})
?.toPromise();

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpEvent, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { NEVER, Observable, throwError } from 'rxjs';
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
import { catchError, mergeMap, tap } from 'rxjs/operators';
import { AuthService } from '@core/auth';
@@ -25,6 +25,7 @@ export class HttpErrorInterceptor implements HttpInterceptor {
})
.afterClosed$.pipe(mergeMap(() => throwError(error)));
} else if (error.status === 401) {
console.log('401', error);
return this._modal
.open({
content: UiMessageModalComponent,
@@ -35,7 +36,7 @@ export class HttpErrorInterceptor implements HttpInterceptor {
tap(() => {
this._auth.login();
}),
mergeMap(() => throwError(error))
mergeMap(() => NEVER)
);
}

View File

@@ -0,0 +1,3 @@
<shell-root>
<router-outlet></router-outlet>
</shell-root>

View File

@@ -0,0 +1,10 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-main',
templateUrl: 'main.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MainComponent {
constructor() {}
}

View File

@@ -1 +1,10 @@
<shared-branch-selector [value]="selectedBranch$ | async" (valueChange)="setNewBranch($event)"></shared-branch-selector>
<h1>Platform: {{ platform | json }}</h1>
<br />
<h1>{{ appVersion }}</h1>
<br />
<h1>{{ userAgent }}</h1>
<br />
<h1>Navigator: {{ navigator | json }}</h1>
<br />
<br />
<h1>Device: {{ device }}</h1>

View File

@@ -1,3 +1,4 @@
import { Platform, PlatformModule } from '@angular/cdk/platform';
import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { BranchSelectorComponent } from '@shared/components/branch-selector';
@@ -8,13 +9,48 @@ import { BehaviorSubject } from 'rxjs';
selector: 'app-preview',
templateUrl: 'preview.component.html',
styleUrls: ['preview.component.css'],
imports: [CommonModule, BranchSelectorComponent],
imports: [CommonModule, BranchSelectorComponent, PlatformModule],
standalone: true,
})
export class PreviewComponent implements OnInit {
selectedBranch$ = new BehaviorSubject<BranchDTO>({});
constructor() {}
get appVersion() {
return 'App Version: ' + (window.navigator as any).appVersion;
}
get userAgent() {
return 'User Agent: ' + (window.navigator as any).userAgent;
}
get navigator() {
const nav = {};
for (let i in window.navigator) nav[i] = navigator[i];
return nav;
}
get platform() {
return this._platform;
}
get device() {
const isIpadNative = this._platform.IOS && !this._platform.SAFARI;
const isIpadMini6Native = window?.navigator?.userAgent?.includes('Macintosh') && !this._platform.SAFARI;
const isNative = isIpadNative || isIpadMini6Native;
const isPWA = this._platform.IOS && this._platform.SAFARI;
const isDesktop = !isNative && !isPWA;
if (isNative) {
if (isIpadMini6Native) {
return 'IPAD mini 6 Native App';
} else if (isIpadNative) {
return 'IPAD mini 2 Native App or IPAD mini 5 Native App';
}
} else if (isPWA) {
return 'IPAD Safari PWA';
} else if (isDesktop) return 'Desktop or Macintosh';
}
constructor(private readonly _platform: Platform) {}
ngOnInit() {}

View File

@@ -1,4 +0,0 @@
// start:ng42.barrel
export * from './shell.component';
export * from './shell.module';
// end:ng42.barrel

View File

@@ -1,74 +0,0 @@
<div class="shell-header-wrapper">
<shell-header [section]="section$ | async" (sectionChange)="setSection($event)">
<a [routerLink]="['/kunde/dashboard']" routerLinkActive="active" class="dashboard-btn">
<ui-icon icon="dashboard" size="26px"></ui-icon>
</a>
<button class="notifications-btn" [disabled]="(notificationCount$ | async) === 0" (click)="openNotifications()">
<ui-icon icon="notification" size="26px"></ui-icon>
<span class="notification-counter" *ngIf="notificationCount$ | async; let count">{{ count }}</span>
</button>
<button (click)="logout()" class="logout-btn">
<span *ngIf="currentBranch$ | async; let currentBranch">{{ currentBranch.key | uppercase }}</span>
<ui-icon icon="logout" size="26px"></ui-icon>
</button>
</shell-header>
</div>
<div class="shell-process-wrapper">
<shell-process
[label]="addProcessLabel$ | async"
[canAddProcess]="canAddProcess$ | async"
(addProcess)="addProcess(); processTabs?.last?.triggerAnimation()"
>
<shell-process-tab
#processTabs
(activateProcess)="activateProcess($event)"
(closeProcess)="closeProcess($event)"
(processAction)="processAction($event)"
*ngFor="let process of processes$ | async; trackBy: trackByIdFn"
[isActive]="(activatedProcessId$ | async) === process.id"
[process]="process"
></shell-process-tab>
</shell-process>
</div>
<div class="main-wrapper">
<main>
<router-outlet></router-outlet>
</main>
</div>
<div class="shell-footer-wrapper">
<shell-footer *ngIf="section$ | async; let section">
<ng-container *ngIf="section === 'customer'">
<a (click)="resetSelectedBranch()" [routerLink]="[customerBasePath$ | async, 'product']" routerLinkActive="active">
<ui-icon icon="catalog" size="30px"></ui-icon>
Artikelsuche
</a>
<a [routerLink]="[customerBasePath$ | async, 'customer']" routerLinkActive="active">
<ui-icon icon="customer" size="24px"></ui-icon>
Kundensuche
</a>
<a [routerLink]="[customerBasePath$ | async, 'goods', 'out']" routerLinkActive="active">
<ui-icon icon="box_out" size="24px"></ui-icon>
Warenausgabe
</a>
</ng-container>
<ng-container *ngIf="section === 'branch'">
<a [routerLink]="['/filiale/task-calendar']" routerLinkActive="active">
<ui-icon icon="calendar_check" size="24px"></ui-icon>
Tätigkeitskalender
</a>
<a [routerLink]="['/filiale/goods/in']" routerLinkActive="active">
<ui-icon icon="box_return" size="24px"></ui-icon>
Abholfach
</a>
<a [routerLink]="[remissionUrl$ | async]" [queryParams]="remissionQueryParams$ | async" routerLinkActive="active">
<ui-icon icon="documents_refresh" size="24px"></ui-icon>
Remission
</a>
<a [routerLink]="['/filiale/package-inspection']" routerLinkActive="active" (click)="fetchAndOpenPackages()">
<ui-svg-icon icon="clipboard-check-outline" [size]="24"></ui-svg-icon>
Wareneingang
</a>
</ng-container>
</shell-footer>
</div>

View File

@@ -1,60 +0,0 @@
:host {
@apply block;
}
.main-wrapper {
@apply fixed right-0 left-0 overflow-auto;
top: 8.375rem;
bottom: 5rem;
main {
@apply w-full max-w-content mx-auto px-4 self-stretch;
}
}
.shell-header-wrapper {
@apply fixed top-0 left-0 right-0 bg-white;
shell-header {
@apply w-full max-w-content mx-auto;
}
button.notifications-btn {
@apply relative;
.notification-counter {
@apply absolute flex items-center justify-center top-2 right-px-3 text-sm rounded-full w-6 h-6 font-semibold;
background-color: var(--shell-notification-counter-background);
color: var(--shell-notification-counter-text);
z-index: 10;
}
}
}
.shell-process-wrapper {
@apply fixed left-0 right-0 bg-white;
top: 5.125rem;
shell-process {
@apply w-full max-w-content mx-auto;
height: 52px;
}
}
shell-process {
height: 52px;
grid-area: process;
}
.shell-footer-wrapper {
@apply fixed bottom-0 left-0 right-0 bg-white z-fixed shadow-card;
shell-footer {
@apply w-full max-w-content mx-auto;
.active {
@apply font-bold;
color: var(--shell-footer-link-active);
}
}
}

View File

@@ -1,430 +0,0 @@
// unit test ShellComponent with Spectator
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { AuthService } from '@core/auth';
import { Config } from '@core/config';
import { BreadcrumbService } from '@core/breadcrumb';
import { DomainAvailabilityService } from '@domain/availability';
import { DomainDashboardService } from '@domain/isa';
import { NotificationsHub } from '@hub/notifications';
import { ModalNotificationsComponent } from '@modal/notifications';
import { Spectator, createComponentFactory, SpyObject, createSpyObject } from '@ngneat/spectator';
import { DashboardComponent } from '@page/dashboard';
import { ShellFooterComponent } from '@shell/footer';
import { ShellHeaderComponent } from '@shell/header';
import { ShellProcessComponent, ShellProcessTabComponent } from '@shell/process';
import { UiIconComponent } from '@ui/icon';
import { UiModalService } from '@ui/modal';
import { EnvelopeDTO, MessageBoardItemDTO } from 'apps/hub/notifications/src/lib/defs';
import { MockComponent } from 'ng-mocks';
import { of } from 'rxjs';
import { first } from 'rxjs/operators';
import { ShellComponent } from './shell.component';
import { WrongDestinationModalService } from 'apps/page/package-inspection/src/lib/components/wrong-destination-modal/wrong-destination-modal.service';
// DummyComponent Class
@Component({
selector: 'dummy-component',
template: '<div></div>',
})
class DummyComponent {
constructor() {}
}
describe('ShellComponent', () => {
let spectator: Spectator<ShellComponent>;
let applicationServiceMock: SpyObject<ApplicationService>;
let modalServiceMock: SpyObject<UiModalService>;
let notificationsHubMock: SpyObject<NotificationsHub>;
let router: Router;
let breadcrumbServiceMock: SpyObject<BreadcrumbService>;
let authServiceMock: SpyObject<AuthService>;
const createComponent = createComponentFactory({
component: ShellComponent,
imports: [
RouterTestingModule.withRoutes([
{ path: 'kunde', component: DummyComponent },
{ path: 'kunde/dashboard', component: DashboardComponent },
]),
],
declarations: [
MockComponent(ShellHeaderComponent),
MockComponent(ShellFooterComponent),
MockComponent(ShellProcessComponent),
MockComponent(ShellProcessTabComponent),
MockComponent(UiIconComponent),
],
mocks: [BreadcrumbService, DomainAvailabilityService, AuthService, DomainDashboardService, Config, WrongDestinationModalService],
});
beforeEach(() => {
applicationServiceMock = createSpyObject(ApplicationService);
applicationServiceMock.getSection$.and.returnValue(of('customer'));
applicationServiceMock.getProcesses$.and.returnValue(of([]));
applicationServiceMock.getProcessById$.and.returnValue(of({ id: 4000 }));
applicationServiceMock.getActivatedProcessId$.and.returnValue(of(undefined));
applicationServiceMock.getLastActivatedProcessWithSectionAndType$.and.returnValue(of({}));
applicationServiceMock.getLastActivatedProcessWithSection$.and.returnValue(of({}));
notificationsHubMock = createSpyObject(NotificationsHub);
notificationsHubMock.notifications$ = of({});
modalServiceMock = createSpyObject(UiModalService);
authServiceMock = createSpyObject(AuthService);
spectator = createComponent({
providers: [
{ provide: ApplicationService, useValue: applicationServiceMock },
{ provide: NotificationsHub, useValue: notificationsHubMock },
{ provide: UiModalService, useValue: modalServiceMock },
{ provide: AuthService, useValue: authServiceMock },
],
});
breadcrumbServiceMock = spectator.inject(BreadcrumbService);
router = spectator.inject(Router);
});
it('should create', () => {
expect(spectator.component).toBeTruthy();
});
describe('shell-header', () => {
it('should call setSection() on sectionChange event with the section argument', () => {
spyOn(spectator.component, 'setSection');
spectator.triggerEventHandler('shell-header', 'sectionChange', 'branch');
expect(spectator.component.setSection).toHaveBeenCalledWith('branch');
});
it('should render the header buttons', () => {
// Test verhält sich anders, wenn die größe des Browserfensters kleiner ist als 640px, da
// die Buttons dann unsichtbar werden und ins Drei-Punkt Menü verschoben werden.
if (document.body.clientWidth > 639) {
expect(spectator.query('shell-header .notifications-btn')).toBeVisible();
expect(spectator.query('shell-header .dashboard-btn')).toBeVisible();
expect(spectator.query('shell-header .logout-btn')).toBeVisible();
} else {
expect(spectator.query('shell-header .notifications-btn')).not.toBeVisible();
expect(spectator.query('shell-header .dashboard-btn')).not.toBeVisible();
expect(spectator.query('shell-header .logout-btn')).not.toBeVisible();
}
});
it('should have a anchor tag which navigates to /kunde/dashboard', () => {
const anchor = spectator.query('shell-header a');
expect(anchor).toHaveAttribute('href', '/kunde/dashboard');
});
});
describe('shell-process', () => {
it('should call addProcess() on addProcess event', () => {
spyOn(spectator.component, 'addProcess');
spectator.triggerEventHandler('shell-process', 'addProcess', undefined);
expect(spectator.component.addProcess).toHaveBeenCalled();
});
describe('shell-process-tab', () => {
it('should render for each process', () => {
const processes = [{}, {}, {}];
applicationServiceMock.getSection$.and.returnValue(of('customer'));
applicationServiceMock.getProcesses$.and.returnValue(of(processes));
spectator.detectComponentChanges();
expect(spectator.queryAll('shell-process-tab')).toHaveLength(processes.length);
});
it('should call activateProcess() on activateProcess event', () => {
const processes = [{ id: 1 }];
applicationServiceMock.getProcesses$.and.returnValue(of(processes));
spectator.detectComponentChanges();
spyOn(spectator.component, 'activateProcess');
spectator.triggerEventHandler('shell-process-tab', 'activateProcess', processes[0].id);
expect(spectator.component.activateProcess).toHaveBeenCalledWith(1);
});
it('should call closeProcess() on closeProcess event', () => {
const processes = [{ id: 1 }];
applicationServiceMock.getProcesses$.and.returnValue(of(processes));
spectator.detectComponentChanges();
spyOn(spectator.component, 'closeProcess');
spectator.triggerEventHandler('shell-process-tab', 'closeProcess', processes[0].id);
expect(spectator.component.closeProcess).toHaveBeenCalledWith(1);
});
});
});
describe('shell-footer', () => {
it('should render when section is set', () => {
applicationServiceMock.getSection$.and.returnValue(of('customer'));
spectator.detectComponentChanges();
expect(spectator.query('shell-footer')).toBeVisible();
});
it('should not render when section is undefined', () => {
applicationServiceMock.getSection$.and.returnValue(of(undefined));
spectator.detectComponentChanges();
expect(spectator.query('shell-footer')).not.toBeVisible();
});
it('should display the menu items for section customer', () => {
applicationServiceMock.getSection$.and.returnValue(of('customer'));
spectator.component.customerBasePath$ = of('/kunde/1');
spectator.detectComponentChanges();
const anchors = spectator.queryAll('shell-footer a');
expect(anchors[0]).toHaveText('Artikelsuche');
expect(anchors[0]).toHaveAttribute('href', '/kunde/1/product');
expect(anchors[1]).toHaveText('Kundensuche');
expect(anchors[1]).toHaveAttribute('href', '/kunde/1/customer');
expect(anchors[2]).toHaveText('Warenausgabe');
expect(anchors[2]).toHaveAttribute('href', '/kunde/1/goods/out');
});
it('should display the menu items for section branch', () => {
applicationServiceMock.getSection$.and.returnValue(of('branch'));
spectator.detectComponentChanges();
const anchors = spectator.queryAll('shell-footer a');
expect(anchors[0]).toHaveText('Tätigkeitskalender');
expect(anchors[0]).toHaveAttribute('href', '/filiale/task-calendar');
expect(anchors[1]).toHaveText('Abholfach');
expect(anchors[1]).toHaveAttribute('href', '/filiale/goods/in');
expect(anchors[2]).toHaveText('Remission');
expect(anchors[2]).toHaveAttribute('href', '/filiale/remission');
});
});
describe('activatedProcessId$', () => {
it('should call _appService.getActivatedProcessId$() and return its value', async () => {
applicationServiceMock.getActivatedProcessId$.and.returnValue(of(1));
const processId = await spectator.component.activatedProcessId$.pipe(first()).toPromise();
expect(processId).toBe(1);
expect(applicationServiceMock.getActivatedProcessId$).toHaveBeenCalled();
});
});
describe('section$', () => {
it('should call _appService.getSection$() and return its value', async () => {
applicationServiceMock.getSection$.and.returnValue(of('branch'));
const section = await spectator.component.section$.pipe(first()).toPromise();
expect(section).toBe('branch');
expect(applicationServiceMock.getSection$).toHaveBeenCalled();
});
});
describe('processes$', () => {
it('should call _appService.processes$() and return its value', async () => {
applicationServiceMock.getProcesses$.and.returnValue(of([{}, {}]));
const processes = await spectator.component.processes$.pipe(first()).toPromise();
expect(processes).toHaveLength(2);
expect(applicationServiceMock.getProcesses$).toHaveBeenCalledWith('customer');
});
});
describe('remissionProcess$', () => {
it('should call _appService.getProcessById$() with Remission Id and return its value', async () => {
applicationServiceMock.getProcessById$.and.returnValue(of({ id: 4000 }));
await spectator.component.remissionProcess$.pipe(first()).toPromise();
expect(applicationServiceMock.getProcessById$).toHaveBeenCalled();
});
});
describe('remissionUrl$', () => {
it('should return the correct url if process.data.active is available', async () => {
const process = {
id: 4000,
data: {
active: 9999,
},
};
applicationServiceMock.getProcessById$.and.returnValue(of(process));
const url = await spectator.component.remissionUrl$.pipe(first()).toPromise();
expect(url).toBe('/filiale/remission/9999/list');
});
it('should return the correct url if process.data.active is not available', async () => {
const process = {
id: 4000,
data: {},
};
applicationServiceMock.getProcessById$.and.returnValue(of(process));
const url = await spectator.component.remissionUrl$.pipe(first()).toPromise();
expect(url).toBe('/filiale/remission');
});
});
describe('remissionQueryParams$', () => {
it('should return the correct queryParams if process.data.active and process.data.queryParams are available', async () => {
const process = {
id: 4000,
data: {
active: 9999,
queryParams: { filter: 'test' },
},
};
applicationServiceMock.getProcessById$.and.returnValue(of(process));
const queryParams = await spectator.component.remissionQueryParams$.pipe(first()).toPromise();
expect(queryParams).toEqual(process.data.queryParams);
});
it('should return the correct queryParams if process.data.active and process.data.queryParams are not available', async () => {
const process = {
id: 4000,
data: {},
};
applicationServiceMock.getProcessById$.and.returnValue(of(process));
const queryParams = await spectator.component.remissionQueryParams$.pipe(first()).toPromise();
expect(queryParams).toEqual({});
});
});
describe('setSection()', () => {
it('should call _appService.setSection() with the argument section', async () => {
await spectator.component.setSection('customer');
expect(applicationServiceMock.setSection).toHaveBeenCalledWith('customer');
});
it('should call activateProcess if getLastActivatedProcessWithSection returns a value', async () => {
applicationServiceMock.getLastActivatedProcessWithSection$.and.returnValue(of({ id: 1 }));
spyOn(spectator.component, 'activateProcess');
await spectator.component.setSection('customer');
expect(spectator.component.activateProcess).toHaveBeenCalledWith(1);
});
});
describe('logout()', () => {
it('should call _authService.logout()', () => {
spectator.component.logout();
expect(authServiceMock.logout).toHaveBeenCalled();
});
});
describe('addProcess()', () => {
it('should call navigate to /kunde/{timestamp}/product', () => {
spyOn(router, 'navigate');
spyOn(Date, 'now').and.returnValue(123);
spectator.component.addProcess();
expect(router.navigate).toHaveBeenCalledWith(['/kunde', 123, 'product']);
});
});
describe('closeProcess()', () => {
it('should call _appService.removeProcess() with the processId argument', () => {
const processes = [{}, {}, {}];
applicationServiceMock.getSection$.and.returnValue(of('customer'));
applicationServiceMock.getProcesses$.and.returnValue(of(processes));
spectator.component.closeProcess(1);
expect(applicationServiceMock.removeProcess).toHaveBeenCalledWith(1);
});
it('should navigate to kunde/dashboard if no process is available', async () => {
spyOn(router, 'navigate');
applicationServiceMock.getSection$.and.returnValue(of('customer'));
applicationServiceMock.getProcesses$.and.returnValue(of([]));
spectator.detectComponentChanges();
await spectator.component.closeProcess(1);
expect(router.navigate).toHaveBeenCalledWith(['/kunde', 'dashboard']);
});
it('should not navigate to kunde/dashboard if processes are available', async () => {
spyOn(router, 'navigate');
const processes = [
{ id: 1, name: 'test', section: 'customer' },
{ id: 2, name: 'test', section: 'customer' },
];
applicationServiceMock.getLastActivatedProcessWithSection$.and.returnValue(of({}));
applicationServiceMock.getSection$.and.returnValue(of('customer'));
applicationServiceMock.getProcesses$.and.returnValue(of(processes));
await spectator.component.closeProcess(1);
expect(router.navigate).not.toHaveBeenCalledWith(['/kunde', 'dashboard']);
});
it('should activate the next process when it was not the last process', async () => {
spyOn(spectator.component, 'activateProcess');
applicationServiceMock.getLastActivatedProcessWithSection$.and.returnValue(
of({
id: 2,
name: 'test',
section: 'customer',
activated: 2,
})
);
const processes = [
{ id: 1, name: 'test', section: 'customer', activated: 1 },
{ id: 2, name: 'test', section: 'customer', activated: 2 },
];
applicationServiceMock.getSection$.and.returnValue(of('customer'));
applicationServiceMock.getProcesses$.and.returnValue(of(processes));
await spectator.component.closeProcess(1);
expect(spectator.component.activateProcess).toHaveBeenCalledWith(2);
});
});
describe('activateProcess()', () => {
it('should get the last activated breadcrumb by key and if it is defined it navigates to its path with queryParams', async () => {
const crumb = { path: '/kunde/product', params: { id: 1 } };
spyOn(router, 'navigate');
breadcrumbServiceMock.getLastActivatedBreadcrumbByKey$.and.returnValue(of(crumb));
await spectator.component.activateProcess(1);
expect(router.navigate).toHaveBeenCalledWith([crumb.path], { queryParams: crumb.params });
});
it('should navigate to /kunde if no breadcrumb for this process exists', async () => {
breadcrumbServiceMock.getLastActivatedBreadcrumbByKey$.and.returnValue(of(undefined));
spyOn(router, 'navigate');
await spectator.component.activateProcess(1);
expect(router.navigate).toHaveBeenCalledWith(['/kunde', 1, 'product']);
});
});
describe('processAction()', () => {
it('should navigate to cart when process type is cart', () => {
spyOn(router, 'navigate');
const process: ApplicationProcess = { id: 1, name: 'Vorgang', section: 'customer', type: 'cart' };
spectator.component.processAction(process);
expect(router.navigate).toHaveBeenCalledWith(['/kunde', process.id, 'cart']);
});
it('should not navigate to when process type is not cart', () => {
spyOn(router, 'navigate');
const process: ApplicationProcess = { id: 1, name: 'Vorgang', section: 'customer', type: 'goods-out' };
spectator.component.processAction(process);
expect(router.navigate).not.toHaveBeenCalled();
});
});
describe('openNotifications()', () => {
it('should call modalService.open() with the ModalNotificationComponent', async () => {
const notifications: EnvelopeDTO<MessageBoardItemDTO[]> = {
data: [{}, {}, {}],
};
spectator.component.notifications$ = of(notifications);
await spectator.component.openNotifications();
expect(modalServiceMock.open).toHaveBeenCalledWith({
content: ModalNotificationsComponent,
data: notifications,
config: {
showScrollbarY: false,
},
});
});
});
});

View File

@@ -1,177 +0,0 @@
import { Component, ChangeDetectionStrategy, ViewChildren, QueryList, TrackByFunction, NgZone } from '@angular/core';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { first, map, shareReplay, switchMap, take, tap } from 'rxjs/operators';
import { NotificationsHub } from '@hub/notifications';
import { ModalNotificationsComponent } from '@modal/notifications';
import { UiModalService } from '@ui/modal';
import { Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { combineLatest } from 'rxjs';
import { AuthService } from '@core/auth';
import { DomainAvailabilityService } from '@domain/availability';
import { ShellProcessTabComponent } from '@shell/process';
import { Config } from '@core/config';
import { WrongDestinationModalService } from 'apps/page/package-inspection/src/lib/components/wrong-destination-modal/wrong-destination-modal.service';
@Component({
selector: 'app-shell',
templateUrl: 'shell.component.html',
styleUrls: ['shell.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShellComponent {
@ViewChildren('processTabs')
readonly processTabs: QueryList<ShellProcessTabComponent>;
notifications$ = this._notificationsHub.notifications$;
notificationCount$ = this.notifications$.pipe(map((message) => message?.data?.length));
get activatedProcessId$() {
return this._appService.getActivatedProcessId$().pipe(
tap((activatedProcessId) => {
this.processTabs?.find((process) => process?.process?.id === activatedProcessId && !process?.isActive)?.slideIntoView();
})
);
}
customerBasePath$ = this.activatedProcessId$.pipe(
switchMap((processId) => this._appService.getProcessById$(processId)),
map((process) => {
if (!!process && process.section === 'customer' && process.type !== 'cart-checkout') {
// Übernehme aktiven Prozess
return `/kunde/${process.id}`;
} else {
// Über Guards wird ein neuer Prozess erstellt
return '/kunde';
}
})
);
get section$() {
return this._appService.getSection$().pipe(shareReplay());
}
get processes$() {
return this.section$.pipe(switchMap((section) => this._appService.getProcesses$(section)));
}
get remissionProcess$() {
return this._appService.getProcessById$(this._config.get('process.ids.remission'));
}
get remissionUrl$() {
return this.remissionProcess$.pipe(
map((process) => (process?.data?.active ? `/filiale/remission/${process.data.active}/list` : '/filiale/remission'))
);
}
get remissionQueryParams$() {
return this.remissionProcess$.pipe(
map((process) => (process?.data?.active && process?.data?.queryParams ? process.data.queryParams : {}))
);
}
get addProcessLabel$() {
return combineLatest([this.section$, this.processes$]).pipe(
map(([section, processes]) => (section === 'customer' && processes.length === 0 ? 'VORGANG STARTEN' : ''))
);
}
get canAddProcess$() {
return this.section$.pipe(map((section) => section === 'customer'));
}
get currentBranch$() {
return this._availabilityService.getDefaultBranch();
}
constructor(
private readonly _appService: ApplicationService,
private readonly _config: Config,
private readonly _notificationsHub: NotificationsHub,
private readonly _modal: UiModalService,
private readonly _router: Router,
private readonly _breadcrumbService: BreadcrumbService,
private readonly _authService: AuthService,
private readonly _availabilityService: DomainAvailabilityService,
private readonly _zone: NgZone,
private readonly _wrongDestinationModalService: WrongDestinationModalService
) {}
async setSection(section: 'customer' | 'branch') {
this._appService.setSection(section);
const lastProcessId = (await this._appService.getLastActivatedProcessWithSection$(section).pipe(first()).toPromise())?.id;
if (lastProcessId) {
this.activateProcess(lastProcessId);
} else {
this._router.navigate([section === 'customer' ? '/kunde' : '/filiale']);
}
}
// Process werden über Guards erstellt und aktiviert. An dieser Stelle wird nur navigiert
async addProcess() {
const processId = Date.now();
await this._router.navigate(['/kunde', processId, 'product']);
}
async activateProcess(activatedProcessId: number) {
try {
const latestCrumb = await this._breadcrumbService?.getLastActivatedBreadcrumbByKey$(activatedProcessId)?.pipe(take(1)).toPromise();
await this._zone.run(async () => {
if (latestCrumb) {
await this._router.navigate([latestCrumb.path], { queryParams: latestCrumb.params });
} else {
await this._router.navigate(['/kunde', activatedProcessId, 'product']);
}
});
} catch (error) {}
}
async closeProcess(processId: number) {
this._appService.removeProcess(processId);
const processes = await this.processes$.pipe(first()).toPromise();
if (processes.length === 0) {
await this._router.navigate(['/kunde', 'dashboard']);
return;
}
const section = await this.section$.pipe(first()).toPromise();
const lastActivatedProcess = await this._appService.getLastActivatedProcessWithSection$(section).pipe(first()).toPromise();
this.activateProcess(lastActivatedProcess?.id);
}
processAction(process: ApplicationProcess) {
if (process?.type === 'cart') {
this._router.navigate(['/kunde', process.id, 'cart']);
}
}
async logout() {
await this._authService.logout();
}
async openNotifications() {
const notifications = await this.notifications$.pipe(first()).toPromise();
this._modal.open({
content: ModalNotificationsComponent,
data: notifications,
config: {
showScrollbarY: false,
},
});
}
async resetSelectedBranch() {
const processId = await this.activatedProcessId$.pipe(take(1)).toPromise();
if (!!processId) {
this._appService.patchProcessData(processId, { selectedBranch: undefined });
}
}
trackByIdFn: TrackByFunction<ApplicationProcess> = (_, process) => process.id;
fetchAndOpenPackages = () => this._wrongDestinationModalService.fetchAndOpen();
}

View File

@@ -1,19 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { OverlayModule } from '@angular/cdk/overlay';
import { ShellHeaderModule } from '@shell/header';
import { ShellProcessModule } from '@shell/process';
import { ShellFooterModule } from '@shell/footer';
import { ShellComponent } from './shell.component';
import { UiIconModule } from '@ui/icon';
import { RouterModule } from '@angular/router';
@NgModule({
imports: [RouterModule, CommonModule, ShellHeaderModule, ShellProcessModule, ShellFooterModule, UiIconModule, OverlayModule],
exports: [ShellComponent],
declarations: [ShellComponent],
providers: [],
})
export class ShellModule {}

View File

@@ -1,20 +0,0 @@
@font-face {
font-family: 'Material Symbols Outlined';
font-style: normal;
font-weight: 100 700;
src: url(./materials-icons-outlined.woff2) format('woff2');
}
@font-face {
font-family: 'Material Symbols Rounded';
font-style: normal;
font-weight: 100 700;
src: url(./materials-icons-rounded.woff2) format('woff2');
}
@font-face {
font-family: 'Material Symbols Sharp';
font-style: normal;
font-weight: 100 700;
src: url(./materials-icons-sharp.woff2) format('woff2');
}

View File

Binary file not shown.

View File

File diff suppressed because one or more lines are too long

View File

@@ -1,14 +1,16 @@
{
"title": "ISA - Integration",
"silentRefresh": {
"interval": 300000
},
"@cdn/product-image": {
"url": "https://produktbilder.paragon-data.net"
},
"@core/auth": {
"issuer": "https://sso-test.paragon-data.de",
"clientId": "hug-isa",
"responseType": "id_token token",
"oidc": true,
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi"
"clientId": "isa-client",
"responseType": "code",
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi isa-wws-webapi"
},
"@core/logger": {
"logLevel": "debug"
@@ -38,7 +40,7 @@
"rootUrl": "https://filialinformationsystem-integration.paragon-systems.de/eiswebapi/v1"
},
"@swagger/remi": {
"rootUrl": "https://isa-integration.paragon-data.net/inv/v1"
"rootUrl": "https://isa-integration.paragon-data.net/inv/v6"
},
"@swagger/wws": {
"rootUrl": "https://isa-integration.paragon-data.net/wws/v1"
@@ -60,7 +62,8 @@
"goodsIn": 2000,
"taskCalendar": 3000,
"remission": 4000,
"packageInspection": 5000
"packageInspection": 5000,
"assortment": 6000
}
},
"checkForUpdates": 3600000,

View File

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,8 @@
{
"title": "ISA - Production",
"silentRefresh": {
"interval": 300000
},
"@cdn/product-image": {
"url": "https://produktbilder.paragon-data.net"
},
@@ -8,7 +11,7 @@
"clientId": "hug-isa",
"responseType": "id_token token",
"oidc": true,
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi"
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi isa-wws-webapi"
},
"@core/logger": {
"logLevel": "debug"
@@ -38,10 +41,10 @@
"rootUrl": "https://filialinformationsystem.paragon-systems.de/eiswebapi/v1"
},
"@swagger/remi": {
"rootUrl": "https://isa.paragon-systems.de/inv/v1"
"rootUrl": "https://isa.paragon-systems.de/inv/v6"
},
"@swagger/wws": {
"rootUrl": "https://isa.paragon-data.net/wws/v1"
"rootUrl": "https://isa.paragon-systems.de/wws/v1"
},
"hubs": {
"notifications": {
@@ -60,11 +63,12 @@
"goodsIn": 2000,
"taskCalendar": 3000,
"remission": 4000,
"packageInspection": 5000
"packageInspection": 5000,
"assortment": 6000
}
},
"checkForUpdates": 3600000,
"licence": {
"scandit": ""
"scandit": "AfHi/mY+RbwJD5nC7SuWn3I14pFUOfSbQ2QG//4aV3zWQjwix30kHqsqraA8ZiipDBql8YlwIyV6VPBMUiAX4s9YHDxHHsWwq2BUB3ImzDEcU1jmMH/5yakGUYpCQ68D0iZ8SG9sS0QBb3iFdCHc1r9DFr1cMTxM7zOvb/AUoIVmieHZXnx9ioUgCvczsLiuX3hwvTW3lhbvJ4uUyqTWK4sWFVwoY4AIWSFrPwwrkV2DksMKT5fMJT3GWgPypvTIGwWvpRfLWwKlc1Z3ckyb84khsnaWD2wr+hdgu/K8YIMmgGszm5KIZ/G05YfDNZtQ4jby+5RZvQwWR8rxM35rJgf73OkMSpuL9jw3T0TTAlvpkGRLzVVuCw9VjlBLqfPNEZ6VsEwFuAla9IYUvFHCsjypg2J6UpxHXrTYmbsSu5Jm8frVfS5znPPTO9D/4rF6ZVv2PxY9PgUgJUvwMa/VMc/nse3RRRf8RGT4rUItfJDFO8pujD76vVEWq/KixQRoMdLgDLyxhsFVftkxqhZhyEfFZzsEy49LSojJ28vpHpBWLeCQBmnZ7JZ4C5yOQiqSQV/assBq2zJN2q+vCDp8qy5j1rED1SX5Ec7JpgpgnU4chLIf5Zn7bP/hNGT3pEYBuXeDXXN8ke1pcc3fc3m0FysDG0o56XVCUqImZ8Ezi8eujZciKDrWbtljhKTj7cnfuJx0sVHF6Bh5i4YfgA/Z+NL+MtH2EVIF67e6hEz6PWYTcoh3ybBaJfxb2FNvGJutNKg04GwMhYq6K2IddBt0fDiBt0SGM0oSBlUP3DKCUmXcf2a6ASbrcqv6Wz1jHt0pY4U8bEpg7qSbW3VDyvdPgyQ="
}
}

View File

@@ -1,5 +1,8 @@
{
"title": "ISA - Staging",
"silentRefresh": {
"interval": 300000
},
"@cdn/product-image": {
"url": "https://produktbilder.paragon-data.net"
},
@@ -8,7 +11,7 @@
"clientId": "hug-isa",
"responseType": "id_token token",
"oidc": true,
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi"
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi isa-wws-webapi"
},
"@core/logger": {
"logLevel": "debug"
@@ -38,10 +41,10 @@
"rootUrl": "https://filialinformationsystem-staging.paragon-systems.de/eiswebapi/v1"
},
"@swagger/remi": {
"rootUrl": "https://isa-staging.paragon-systems.de/inv/v1"
"rootUrl": "https://isa-staging.paragon-systems.de/inv/v6"
},
"@swagger/wws": {
"rootUrl": "https://isa-staging.paragon-data.net/wws/v1"
"rootUrl": "https://isa-staging.paragon-systems.de/wws/v1"
},
"hubs": {
"notifications": {
@@ -60,11 +63,12 @@
"goodsIn": 2000,
"taskCalendar": 3000,
"remission": 4000,
"packageInspection": 5000
"packageInspection": 5000,
"assortment": 6000
}
},
"checkForUpdates": 3600000,
"licence": {
"scandit": ""
"scandit": "AfHi/mY+RbwJD5nC7SuWn3I14pFUOfSbQ2QG//4aV3zWQjwix30kHqsqraA8ZiipDBql8YlwIyV6VPBMUiAX4s9YHDxHHsWwq2BUB3ImzDEcU1jmMH/5yakGUYpCQ68D0iZ8SG9sS0QBb3iFdCHc1r9DFr1cMTxM7zOvb/AUoIVmieHZXnx9ioUgCvczsLiuX3hwvTW3lhbvJ4uUyqTWK4sWFVwoY4AIWSFrPwwrkV2DksMKT5fMJT3GWgPypvTIGwWvpRfLWwKlc1Z3ckyb84khsnaWD2wr+hdgu/K8YIMmgGszm5KIZ/G05YfDNZtQ4jby+5RZvQwWR8rxM35rJgf73OkMSpuL9jw3T0TTAlvpkGRLzVVuCw9VjlBLqfPNEZ6VsEwFuAla9IYUvFHCsjypg2J6UpxHXrTYmbsSu5Jm8frVfS5znPPTO9D/4rF6ZVv2PxY9PgUgJUvwMa/VMc/nse3RRRf8RGT4rUItfJDFO8pujD76vVEWq/KixQRoMdLgDLyxhsFVftkxqhZhyEfFZzsEy49LSojJ28vpHpBWLeCQBmnZ7JZ4C5yOQiqSQV/assBq2zJN2q+vCDp8qy5j1rED1SX5Ec7JpgpgnU4chLIf5Zn7bP/hNGT3pEYBuXeDXXN8ke1pcc3fc3m0FysDG0o56XVCUqImZ8Ezi8eujZciKDrWbtljhKTj7cnfuJx0sVHF6Bh5i4YfgA/Z+NL+MtH2EVIF67e6hEz6PWYTcoh3ybBaJfxb2FNvGJutNKg04GwMhYq6K2IddBt0fDiBt0SGM0oSBlUP3DKCUmXcf2a6ASbrcqv6Wz1jHt0pY4U8bEpg7qSbW3VDyvdPgyQ="
}
}

View File

File diff suppressed because one or more lines are too long

View File

@@ -7,7 +7,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
<link href="/assets/fonts/fonts.css" rel="stylesheet" />
<link href="/assets/icons/icons.css" rel="stylesheet" />
<link rel="manifest" href="manifest.webmanifest" />
<meta name="theme-color" content="#1976d2" />
</head>

View File

@@ -5,12 +5,36 @@ import * as moment from 'moment';
moment.locale('de');
import { AppModule } from './app/app.module';
import { DebugService } from './app/debug/debug.service';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic()
const debugService = new DebugService();
const consoleLog = console.log;
console.log = (...args) => {
debugService.add({ type: 'log', args });
consoleLog(...args);
};
const consoleWarn = console.warn;
console.warn = (...args) => {
debugService.add({ type: 'warn', args });
consoleWarn(...args);
};
const consoleError = console.error;
console.error = (...args) => {
debugService.add({ type: 'error', args });
consoleError(...args);
};
platformBrowserDynamic([{ provide: DebugService, useValue: debugService }])
.bootstrapModule(AppModule)
.catch((err) => console.error(err));

View File

@@ -16,7 +16,14 @@
/***************************************************************************************************
* BROWSER POLYFILLS
*/ // Run `npm install --save web-animations-js`.
*/
/**
* Web Animations `@angular/platform-browser/animations`
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/
import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents

View File

@@ -1,7 +1,29 @@
<html>
<body>
<script>
parent.postMessage(location.hash, location.origin);
var checks = [/[\?|&|#]code=/, /[\?|&|#]error=/, /[\?|&|#]token=/, /[\?|&|#]id_token=/];
function isResponse(str) {
if (!str) return false;
for (var i = 0; i < checks.length; i++) {
if (str.match(checks[i])) return true;
}
return false;
}
var message = isResponse(location.hash) ? location.hash : '#' + location.search;
if (window.parent && window.parent !== window) {
// if loaded as an iframe during silent refresh
window.parent.postMessage(message, location.origin);
} else if (window.opener && window.opener !== window) {
// if loaded as a popup during initial login
window.opener.postMessage(message, location.origin);
} else {
// last resort for a popup which has been through redirects and can't use window.opener
localStorage.setItem('auth_hash', message);
localStorage.removeItem('auth_hash');
}
</script>
</body>
</html>

View File

@@ -16,7 +16,7 @@
}
body {
background: var(--bg-color);
@apply bg-background;
}
@layer base {
@@ -32,7 +32,7 @@ body {
}
.desktop .scroll-bar::-webkit-scrollbar-track {
@apply my-4;
// @apply my-4;
-webkit-box-shadow: inset 0 0 4px rgba(0, 0, 0, 0.1);
border-radius: 10px;
background-color: white;

View File

@@ -7,6 +7,7 @@ import { combineLatest } from 'rxjs';
import { filter, map, shareReplay, switchMap, tap } from 'rxjs/operators';
import { geoDistance } from '@utils/common';
import { BehaviorSubject } from 'rxjs';
import { ApplicationService } from '@core/application';
@Component({
selector: 'modal-availabilities',
@@ -20,7 +21,11 @@ export class ModalAvailabilitiesComponent {
stockFetching$ = new BehaviorSubject(true);
item = this.modalRef.data.item;
itemId = this.modalRef.data.itemId || this.modalRef.data.item.id;
userbranch$ = this.domainAvailabilityService.getDefaultBranch();
userbranch$ = combineLatest([
this.applicationService.getSelectedBranch$(this.applicationService.activatedProcessId),
this.domainAvailabilityService.getDefaultBranch(),
]).pipe(map(([selectedBranch, defaultBranch]) => selectedBranch || defaultBranch));
branches$ = this.domainAvailabilityService.getBranches();
filteredBranches$ = combineLatest([this.branches$, this.userbranch$, this.search$]).pipe(
@@ -66,7 +71,8 @@ export class ModalAvailabilitiesComponent {
constructor(
private modalRef: UiModalRef<BranchDTO, { item: ItemDTO; itemId: number }>,
private domainAvailabilityService: DomainAvailabilityService
private domainAvailabilityService: DomainAvailabilityService,
private applicationService: ApplicationService
) {}
filter(query: string) {

View File

@@ -1,4 +1,5 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ApplicationService } from '@core/application';
import { DomainAvailabilityService } from '@domain/availability';
import { DomainOmsService } from '@domain/oms';
import { ComponentStore } from '@ngrx/component-store';
@@ -68,17 +69,15 @@ export class ReorderModalComponent extends ComponentStore<GoodsInListReorderModa
)
);
readonly currentBranch$ = this.domainAvailabilityService.getDefaultBranch();
readonly storeAvailabilities$ = combineLatest([this.orderItem$, this.currentBranch$]).pipe(
switchMap(([item, branch]) =>
readonly storeAvailabilities$ = this.orderItem$.pipe(
switchMap((item) =>
this.domainAvailabilityService
.getPickUpAvailabilities([
{
qty: item.quantity,
ean: item.product.ean,
itemId: item.product?.catalogProductNumber,
shopId: branch.id,
shopId: item?.targetBranchId,
price: item.retailPrice,
},
])
@@ -99,6 +98,7 @@ export class ReorderModalComponent extends ComponentStore<GoodsInListReorderModa
eans: [item.product.ean],
quantity: item.quantity,
price: item.retailPrice,
branchId: item.targetBranchId,
})
.pipe(
catchError(() => {
@@ -141,7 +141,8 @@ export class ReorderModalComponent extends ComponentStore<GoodsInListReorderModa
constructor(
public modalRef: UiModalRef<ReorderResult, { item: OrderItemListItemDTO; showReasons: boolean }>,
private domainAvailabilityService: DomainAvailabilityService,
private _omsService: DomainOmsService
private _omsService: DomainOmsService,
private _applicationService: ApplicationService
) {
super({
orderItem: modalRef.data?.item,

View File

@@ -1,9 +1,9 @@
import { Injectable } from '@angular/core';
import { Observable, fromEvent, Subject } from 'rxjs';
import { Observable, fromEvent, Subject, ReplaySubject } from 'rxjs';
import { map } from 'rxjs/operators';
import { WindowRef } from './window-ref.service';
import { ScanRequestType } from './scan-request.type';
import { EnvironmentService } from '@core/environment';
import { Platform } from '@angular/cdk/platform';
@Injectable({
providedIn: 'root',
})
@@ -11,11 +11,15 @@ export class NativeContainerService {
private wm: Observable<any>;
public windowMessages = new Subject<any>();
private webViewDetected = false;
private webViewEventRecieved = false;
private browserDetected = false;
constructor(private windowRef: WindowRef, private _environmentService: EnvironmentService) {
private _init$ = new ReplaySubject<boolean>(1);
get isNative() {
return this.webViewEventRecieved;
}
constructor(private windowRef: WindowRef, private _platform: Platform) {
this.defineWindowCallback();
this.wm = fromEvent(this.windowRef.nativeWindow, 'message').pipe(
@@ -27,11 +31,16 @@ export class NativeContainerService {
this.wm.subscribe((data) => {
if (data.status === 'INIT') {
this.webViewEventRecieved = true;
this._init$.next(true);
}
this.windowMessages.next(data);
});
}
init() {
return this._init$.asObservable();
}
public openScanner(scanRequestType: ScanRequestType) {
const scanRequest = {
[scanRequestType]: true,
@@ -46,7 +55,6 @@ export class NativeContainerService {
this.windowRef.nativeWindow.postMessage({ status: 'IN_PROGRESS', data: 'Scan Started' }, '*');
try {
// if (this.isUiWebview() && this.isUiWebview().isNative) {
(this.windowRef.nativeWindow as any).webkit.messageHandlers.scanRequest.postMessage(message);
} catch (error) {
this.windowRef.nativeWindow.postMessage({ status: 'ERROR', data: 'Not a WebView' }, '*');
@@ -54,30 +62,6 @@ export class NativeContainerService {
}
}
public isUiWebview() {
// const navigator = this.windowRef.nativeWindow.navigator as Navigator;
// alert(this.deviceDetector.browser);
// const standalone = (navigator as any).standalone,
// userAgent = navigator.userAgent.toLowerCase(),
// safari = /safari/.test(userAgent),
// ios = /iphone|ipod|ipad/.test(userAgent),
// chrome = /chrome/.test(userAgent) && /Google Inc/.test(navigator.vendor),
// crios = /crios/.test(userAgent);
// this.webViewDetected = ios && !standalone && !safari;
// this.browserDetected = !standalone && (safari || chrome) && !crios;
return {
isSafari: this._environmentService.isSafari(),
isNative: this.webViewEventRecieved,
};
}
public isIpadMini6() {
const width = window.innerWidth > 0 ? window.innerWidth : screen.width;
return width === 744;
}
private defineWindowCallback() {
if (this.windowRef.nativeWindow['scanResults'] === undefined) {
this.windowRef.nativeWindow['scanResults'] = (result) => window.postMessage(result, '*');
@@ -91,6 +75,7 @@ export class NativeContainerService {
try {
(this.windowRef.nativeWindow as any).webkit.messageHandlers.scanRequest.postMessage('PING');
} catch (error) {
this._init$.next(false);
this.windowRef.nativeWindow.postMessage({ status: 'ERROR', data: 'Not a WebView' }, '*');
this.windowRef.nativeWindow.postMessage('PING', '*');
}

View File

@@ -0,0 +1,7 @@
{
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../../dist/page/yellow-pages",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@@ -0,0 +1,3 @@
:host {
@apply block;
}

View File

@@ -0,0 +1,3 @@
<shared-breadcrumb class="my-4" [key]="breadcrumbKey"></shared-breadcrumb>
<router-outlet></router-outlet>

View File

@@ -0,0 +1,67 @@
import { RouterTestingModule } from '@angular/router/testing';
import { BreadcrumbService } from '@core/breadcrumb';
import { Config } from '@core/config';
import { Spectator, createComponentFactory } from '@ngneat/spectator';
import { BreadcrumbComponent } from '@shared/components/breadcrumb';
import { MockComponent } from 'ng-mocks';
import { AssortmentComponent } from './assortment.component';
describe('AssortmentComponent', () => {
let spectator: Spectator<AssortmentComponent>;
const createComponent = createComponentFactory({
component: AssortmentComponent,
imports: [RouterTestingModule],
mocks: [Config, BreadcrumbService],
declarations: [MockComponent(BreadcrumbComponent)],
});
let configMock: jasmine.SpyObj<Config>;
let breadcrumbServiceMock: jasmine.SpyObj<BreadcrumbService>;
beforeEach(async () => {
spectator = createComponent();
await spectator.fixture.whenStable();
configMock = spectator.inject(Config);
breadcrumbServiceMock = spectator.inject(BreadcrumbService);
});
it('should create', () => {
expect(spectator.component).toBeTruthy();
});
describe('get breadcrumbKey(): string', () => {
it('should call Config.get("process.ids.assortment") and return the result', () => {
const expected = 'expected';
configMock.get.and.returnValue(expected);
const actual = spectator.component.breadcrumbKey;
expect(configMock.get).toHaveBeenCalledWith('process.ids.assortment');
expect(actual).toBe(expected);
});
});
describe('ngOnInit()', () => {
it('should call createBreadcrumbIfNotExists()', () => {
const spy = spyOn(spectator.component, 'createBreadcrumbIfNotExists');
spectator.component.ngOnInit();
expect(spy).toHaveBeenCalled();
});
});
describe('template', () => {
it('should render the ShellBreadcrumbComponent and pass the breadcrumbKey as @Input() key', () => {
const expected = 'expected';
configMock.get.and.returnValue(expected);
spectator.detectComponentChanges();
const shellBreadcrumb = spectator.query(BreadcrumbComponent);
expect(shellBreadcrumb).toBeTruthy();
expect(shellBreadcrumb.key).toBe(expected);
});
});
});

View File

@@ -0,0 +1,35 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ApplicationService } from '@core/application';
import { Breadcrumb, BreadcrumbService } from '@core/breadcrumb';
import { Config } from '@core/config';
@Component({
selector: 'page-assortment',
templateUrl: 'assortment.component.html',
styleUrls: ['assortment.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AssortmentComponent implements OnInit {
get breadcrumbKey(): string {
return this._config.get('process.ids.assortment');
}
constructor(private _config: Config, private _breadcrumb: BreadcrumbService, private _app: ApplicationService) {}
ngOnInit() {
this.createBreadcrumbIfNotExists();
this._app.setTitle('Sortiment');
}
async createBreadcrumbIfNotExists(): Promise<void> {
const crumb: Breadcrumb = {
key: this.breadcrumbKey,
name: 'Sortiment',
path: '/filiale/assortment',
section: 'branch',
tags: ['main'],
};
await this._breadcrumb.addBreadcrumbIfNotExists(crumb);
}
}

View File

@@ -0,0 +1,16 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { BreadcrumbModule } from '@shared/components/breadcrumb';
import { routes } from './routes';
import { AssortmentComponent } from './assortment.component';
import { PriceUpdateModule } from './price-update/price-update.module';
@NgModule({
imports: [CommonModule, RouterModule.forChild(routes), BreadcrumbModule, PriceUpdateModule],
exports: [AssortmentComponent],
declarations: [AssortmentComponent],
providers: [],
})
export class AssortmentModule {}

View File

@@ -0,0 +1,7 @@
// start:ng42.barrel
export * from './price-update.component.state';
export * from './price-update.component.store';
export * from './price-update.component';
export * from './price-update.module';
export * from './price-update-list';
// end:ng42.barrel

View File

@@ -0,0 +1,6 @@
// start:ng42.barrel
export * from './price-update-item-loader.component';
export * from './price-update-item.component';
export * from './price-update-list.component';
export * from './price-update-list.module';
// end:ng42.barrel

View File

@@ -0,0 +1,9 @@
:host {
@apply flex flex-row items-center bg-white rounded px-4;
height: 53px;
}
.skeleton {
@apply block bg-gray-300 h-6;
animation: load 1s ease-in-out infinite;
}

View File

@@ -0,0 +1,17 @@
import { createComponentFactory, Spectator } from '@ngneat/spectator';
import { PriceUpdateItemLoaderComponent } from './price-update-item-loader.component';
describe('PriceUpdateItemLoaderComponent', () => {
let spectator: Spectator<PriceUpdateItemLoaderComponent>;
const createComponent = createComponentFactory({
component: PriceUpdateItemLoaderComponent,
});
beforeEach(() => {
spectator = createComponent();
});
it('should create', () => {
expect(spectator.component).toBeTruthy();
});
});

View File

@@ -0,0 +1,13 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
@Component({
selector: 'page-price-update-item-loader',
templateUrl: 'price-update-item-loader.component.html',
styleUrls: ['price-update-item-loader.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PriceUpdateItemLoaderComponent implements OnInit {
constructor() {}
ngOnInit() {}
}

View File

@@ -0,0 +1,24 @@
:host {
@apply flex flex-col w-full;
height: 267px;
}
.page-price-update-item__item-card {
@apply grid grid-flow-col;
grid-template-columns: 63px auto minmax(230px, auto);
box-shadow: 0px 0px 10px rgba(220, 226, 233, 0.5);
}
.page-price-update-item__item-details {
@apply grid grid-flow-row;
grid-template-rows: 27px 70px 27px 27px auto;
}
.page-price-update-item__item-image {
box-shadow: 0px 6px 18px rgba(0, 0, 0, 0.197935);
}
.page-price-update-item__item-addition {
@apply grid grid-flow-row justify-items-end;
grid-template-rows: 27px 27px 41px 52px auto;
}

View File

@@ -0,0 +1,82 @@
<div
class="page-price-update-item__item-header flex flex-row w-full items-center justify-between bg-[rgba(0,128,121,0.15)] mb-px-2 px-5 h-[53px] rounded-t-card"
>
<p class="page-price-update-item__item-instruction font-bold text-lg">{{ item?.task?.instruction }}</p>
<p class="page-price-update-item__item-due-date text-base">
gültig ab <span class="font-bold ml-2">{{ item?.task?.dueDate | date }}</span>
</p>
</div>
<div class="page-price-update-item__item-card p-5 h-[212px] bg-white">
<div class="page-price-update-item__item-thumbnail text-center mr-4 w-[47px] h-[73px]">
<img
class="page-price-update-item__item-image w-[47px] h-[73px]"
loading="lazy"
*ngIf="item?.product?.ean | productImage; let productImage"
[src]="productImage"
[alt]="item?.product?.name"
/>
</div>
<div class="page-price-update-item__item-details">
<div class="page-price-update-item__item-contributors flex flex-row">
{{ environment.isTablet() ? (item?.product?.contributors | substr: 38) : item?.product?.contributors }}
</div>
<div
class="page-price-update-item__item-title font-bold text-2xl"
[class.text-xl]="item?.product?.name?.length >= 35"
[class.text-lg]="item?.product?.name?.length >= 40"
[class.text-md]="item?.product?.name?.length >= 50"
[class.text-sm]="item?.product?.name?.length >= 60"
[class.text-xs]="item?.product?.name?.length >= 100"
>
{{ item?.product?.name }}
</div>
<div class="page-price-update-item__item-format">
<div *ngIf="item?.product?.format && item?.product?.formatDetail" class="font-bold flex flex-row">
<img
class="mr-3"
*ngIf="item?.product?.format !== '--'"
loading="lazy"
src="assets/images/Icon_{{ item?.product?.format }}.svg"
[alt]="item?.product?.formatDetail"
/>
{{ environment.isTablet() ? (item?.product?.formatDetail | substr: 25) : item?.product?.formatDetail }}
</div>
</div>
<div class="page-price-update-item__item-misc">
{{ environment.isTablet() ? (item?.product?.manufacturer | substr: 18) : item?.product?.manufacturer }} | {{ item?.product?.ean }}
<br />
{{ item?.product?.volume }} <span *ngIf="item?.product?.volume && item?.product?.publicationDate">|</span>
{{ publicationDate }}
</div>
</div>
<div class="page-price-update-item__item-addition">
<div class="page-price-update-item__item-product-group-details">{{ item?.product?.productGroupDetails }}</div>
<div class="page-price-update-item__item-compartment">
{{ item?.compartmentInfo?.label }}
</div>
<div class="page-price-update-item__item-price font-bold">
{{ item?.product?.price?.value?.value | currency: 'EUR':'code' }}
</div>
<div class="page-price-update-item__item-select-bullet">
<input *ngIf="isSelectable" [ngModel]="selected$ | async" (ngModelChange)="setSelected()" class="isa-select-bullet" type="checkbox" />
</div>
<div class="page-price-update-item__item-stock flex flex-row font-bold">
<ui-icon class="mt-px-2 mr-1" icon="home" size="1em"></ui-icon>
<span
*ngIf="inStock$ | async; let stock"
[class.skeleton]="stock?.inStock === undefined"
class="min-w-[1rem] text-right inline-block"
>{{ stock?.inStock }}</span
>
x
</div>
</div>
</div>

View File

@@ -0,0 +1,22 @@
import { ApplicationService } from '@core/application';
import { DomainAvailabilityService } from '@domain/availability';
import { createComponentFactory, Spectator } from '@ngneat/spectator';
import { DateAdapter } from '@ui/common';
import { PriceUpdateComponentStore } from '../price-update.component.store';
import { PriceUpdateItemComponent } from './price-update-item.component';
xdescribe('PriceUpdateItemComponent', () => {
let spectator: Spectator<PriceUpdateItemComponent>;
const createComponent = createComponentFactory({
component: PriceUpdateItemComponent,
mocks: [DateAdapter, DomainAvailabilityService, ApplicationService, PriceUpdateComponentStore],
});
beforeEach(() => {
spectator = createComponent();
});
it('should create', () => {
expect(spectator.component).toBeTruthy();
});
});

View File

@@ -0,0 +1,80 @@
import { DatePipe } from '@angular/common';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { ApplicationService } from '@core/application';
import { EnvironmentService } from '@core/environment';
import { DomainAvailabilityService, DomainInStockService } from '@domain/availability';
import { ProductListItemDTO } from '@swagger/wws';
import { DateAdapter } from '@ui/common';
import { debounceTime, filter, map, shareReplay, switchMap } from 'rxjs/operators';
import { PriceUpdateComponentStore } from '../price-update.component.store';
import { ReplaySubject, combineLatest } from 'rxjs';
@Component({
selector: 'page-price-update-item',
templateUrl: 'price-update-item.component.html',
styleUrls: ['price-update-item.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [DatePipe],
})
export class PriceUpdateItemComponent {
private _item$ = new ReplaySubject<ProductListItemDTO>(1);
private _item: ProductListItemDTO;
@Input()
get item() {
return this._item;
}
set item(value) {
this._item = value;
this._item$.next(value);
}
get publicationDate() {
if (!!this.item?.product?.publicationDate) {
const date = this._dateAdapter.parseDate(this.item.product.publicationDate);
if (this._dateAdapter.getDate(date) === 1 && this._dateAdapter.getMonth(date) === 0) {
return this._datePipe.transform(date, 'y');
}
return this._datePipe.transform(date, 'dd. MMMM y');
}
return '';
}
get isSelectable() {
return this._store.isSelectable(this.item);
}
selected$ = this._store.selectedItemUids$.pipe(map((selectedItemUids) => selectedItemUids.includes(this.item?.uId)));
defaultBranch$ = this._availability.getDefaultBranch();
inStock$ = combineLatest([this.defaultBranch$, this._item$]).pipe(
debounceTime(100),
filter(([defaultBranch, item]) => !!defaultBranch && !!item),
switchMap(([defaultBranch, item]) =>
this._stockService.getInStock$({
itemId: Number(item?.product?.catalogProductNumber),
branchId: defaultBranch?.id,
})
),
shareReplay(1)
);
constructor(
private _dateAdapter: DateAdapter,
private _datePipe: DatePipe,
private _availability: DomainAvailabilityService,
public applicationService: ApplicationService,
public environment: EnvironmentService,
private _stockService: DomainInStockService,
private _store: PriceUpdateComponentStore
) {}
setSelected() {
const isSelected = this._store.selectedItemUids.includes(this.item?.uId);
this._store.setSelected({ selected: !isSelected, itemUid: this.item?.uId });
}
}

View File

@@ -0,0 +1,23 @@
:host {
@apply block;
}
cdk-virtual-scroll-viewport {
height: calc(100vh - 500px);
}
.page-price-update-list__print-cta:disabled {
@apply text-inactive-branch;
}
.page-price-update-list__action-wrapper {
@apply grid grid-flow-col gap-4 justify-center my-6 fixed bottom-24 inset-x-0;
}
.page-price-update-list__order-by {
box-shadow: 0px 0px 10px rgba(220, 226, 233, 0.5);
}
::ng-deep page-price-update-list ui-order-by-filter .order-by-filter-button-wrapper .order-by-filter-button {
@apply text-black;
}

View File

@@ -0,0 +1,55 @@
<div class="page-price-update-list__header bg-background-liste flex flex-col items-end py-4">
<button
[disabled]="getSelectableItems().length === 0"
(click)="print()"
type="button"
class="page-price-update-list__print-cta text-lg font-bold text-[#F70400] pr-5 mb-3"
>
Drucken
</button>
<div class="flex flex-row items-center justify-end">
<div *ngIf="getSelectableItems().length > 0" class="text-[#0556B4] font-bold text-sm mr-5">
<ng-container *ngIf="selectedItemUids$ | async; let selectedItems">
<button class="page-price-update-list__cta-unselect-all" *ngIf="selectedItems?.length > 0" type="button" (click)="unselectAll()">
Alle entfernen ({{ selectedItems?.length }})
</button>
<button class="page-price-update-list__cta-select-all" type="button" (click)="selectAll()" *ngIf="selectedItems?.length === 0">
Alle auswählen ({{ getSelectableItems().length }})
</button>
</ng-container>
</div>
<div class="page-price-update-list__items-count inline-flex flex-row items-center pr-5 text-sm">
{{ items?.length ??
0 }}
Titel
</div>
</div>
</div>
<div class="page-price-update-list__order-by h-[53px] flex flex-row items-center justify-center bg-white rounded-t-card mb-px-2">
<ui-order-by-filter [orderBy]="orderBy$ | async" (selectedOrderByChange)="search()"> </ui-order-by-filter>
</div>
<cdk-virtual-scroll-viewport #scrollContainer [itemSize]="267" minBufferPx="1200" maxBufferPx="1200" class="scroll-bar">
<page-price-update-item
*cdkVirtualFor="let item of items; let first; trackBy: trackByFn"
[item]="item"
[class.mt-px-10]="!first"
></page-price-update-item>
<page-price-update-item-loader *ngIf="fetching"> </page-price-update-item-loader>
<div class="h-28"></div>
</cdk-virtual-scroll-viewport>
<div class="page-price-update-list__action-wrapper">
<button
*ngIf="!fetching"
[disabled]="(selectedItemUids$ | async).length === 0 || (loading$ | async)"
class="page-price-update-list__complete-items isa-button isa-cta-button isa-button-primary px-11"
type="button"
(click)="onComplete()"
>
<ui-spinner [show]="loading$ | async">Erledigt</ui-spinner>
</button>
</div>

View File

@@ -0,0 +1,42 @@
import { createComponentFactory, Spectator } from '@ngneat/spectator';
import { MockComponent } from 'ng-mocks';
import { PriceUpdateComponentStore } from '../price-update.component.store';
import { PriceUpdateItemComponent } from './price-update-item.component';
import { PriceUpdateListComponent } from './price-update-list.component';
xdescribe('PriceUpdateListComponent', () => {
let spectator: Spectator<PriceUpdateListComponent>;
let priceUpdateStoreMock: jasmine.SpyObj<PriceUpdateComponentStore>;
const createComponent = createComponentFactory({
component: PriceUpdateListComponent,
declarations: [MockComponent(PriceUpdateItemComponent)],
});
beforeEach(() => {
spectator = createComponent();
priceUpdateStoreMock = spectator.inject(PriceUpdateComponentStore, true);
});
it('should create', () => {
expect(spectator.component).toBeTruthy();
});
it('should render the length of the items in .page-price-update-list__items-count', () => {
spectator.setInput('items', [{ uId: '1' }, { uId: '2' }]);
spectator.detectChanges();
expect(spectator.query('.page-price-update-list__items-count')).toHaveText('2 Titel');
});
it('should render the PriceUpdateItemComponent for each item', () => {
spectator.setInput('items', [{ uId: '1' }, { uId: '2' }]);
spectator.detectChanges();
const listItem = spectator.queryAll(PriceUpdateItemComponent);
expect(listItem).toHaveLength(2);
expect(listItem[0].item).toEqual({ uId: '1' });
expect(listItem[1].item).toEqual({ uId: '2' });
});
});

View File

@@ -0,0 +1,57 @@
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { ChangeDetectionStrategy, Component, Input, ViewChild } from '@angular/core';
import { ProductListItemDTO } from '@swagger/wws';
import { map } from 'rxjs/operators';
import { PriceUpdateComponentStore } from '../price-update.component.store';
@Component({
selector: 'page-price-update-list',
templateUrl: 'price-update-list.component.html',
styleUrls: ['price-update-list.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PriceUpdateListComponent {
@ViewChild('scrollContainer', { static: true })
scrollContainer: CdkVirtualScrollViewport;
@Input()
items: ProductListItemDTO[] = [];
@Input()
fetching: boolean = false;
selectedItemUids$ = this._store.selectedItemUids$;
loading$ = this._store.loading$;
orderBy$ = this._store.filter$.pipe(map((filter) => filter?.orderBy));
trackByFn = (index: number, item: ProductListItemDTO) => item.uId;
constructor(private _store: PriceUpdateComponentStore) {}
getSelectableItems() {
return this._store.items.filter((item) => this._store.isSelectable(item)) ?? [];
}
selectAll() {
const selectedItemUids = this.getSelectableItems().map((item) => item.uId);
this._store.patchState({ selectedItemUids });
}
unselectAll() {
this._store.patchState({ selectedItemUids: [] });
}
print() {
this._store.print();
}
onComplete() {
this._store.complete();
}
search() {
this._store.fetchItems();
}
}

View File

@@ -0,0 +1,29 @@
import { ScrollingModule } from '@angular/cdk/scrolling';
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ProductImageModule } from '@cdn/product-image';
import { UiCommonModule } from '@ui/common';
import { UiOrderByFilterModule } from '@ui/filter';
import { UiIconModule } from '@ui/icon';
import { UiSpinnerModule } from '@ui/spinner';
import { PriceUpdateItemLoaderComponent } from './price-update-item-loader.component';
import { PriceUpdateItemComponent } from './price-update-item.component';
import { PriceUpdateListComponent } from './price-update-list.component';
@NgModule({
imports: [
FormsModule,
CommonModule,
UiCommonModule,
UiIconModule,
ProductImageModule,
ScrollingModule,
UiSpinnerModule,
UiOrderByFilterModule,
],
exports: [PriceUpdateListComponent, PriceUpdateItemComponent, PriceUpdateItemLoaderComponent],
declarations: [PriceUpdateListComponent, PriceUpdateItemComponent, PriceUpdateItemLoaderComponent],
providers: [],
})
export class PriceUpdateListModule {}

View File

@@ -0,0 +1,7 @@
:host {
@apply block;
}
::ng-deep ui-filter-input-options .input-options {
max-height: calc(100vh - 520px);
}

View File

@@ -0,0 +1,58 @@
<div class="flex flex-row items-center h-14 bg-white relative rounded-t font-bold shadow-lg">
<h3 class="text-center grow font-bold text-2xl">Preisänderung</h3>
<button
(click)="filterOverlay.open()"
class="absolute right-0 top-0 h-14 rounded px-5 text-lg bg-cadet-blue flex flex-row flex-nowrap items-center justify-center"
type="button"
>
<ui-svg-icon class="mr-2" icon="filter-variant"></ui-svg-icon>
Filter
</button>
</div>
<page-price-update-list *ngIf="showList$ | async; else noResults" [items]="store.items$ | async" [fetching]="store.fetching$ | async">
</page-price-update-list>
<shell-filter-overlay #filterOverlay class="relative">
<div class="relative">
<button type="button" class="absolute top-4 right-4 text-cadet" (click)="closeFilterOverlay()">
<ui-svg-icon [icon]="'close'" [size]="28"></ui-svg-icon>
</button>
</div>
<h3 class="text-3xl text-center font-bold mt-8">Filter</h3>
<ui-filter
*ngIf="filterOverlay.isOpen"
#filter
class="mx-4"
[filter]="store.pendingFilter$ | async"
(search)="applyFilter()"
[loading]="store.fetching$ | async"
[hint]="hint$ | async"
></ui-filter>
<div class="absolute bottom-8 left-0 right-0 grid grid-flow-col gap-4 justify-center">
<button
type="button"
class="px-6 py-4 font-bold bg-white text-brand border-2 border-solid border-brand rounded-full"
(click)="store.resetPendingFilter()"
>
Filter zurücksetzen
</button>
<button
type="button"
class="px-6 py-4 font-bold bg-brand text-white border-2 border-solid border-brand rounded-full disabled:bg-cadet-blue disabled:cursor-progress disabled:border-cadet-blue"
(click)="applyFilter()"
[disabled]="store.fetching$ | async"
>
<ui-spinner [show]="store.fetching$ | async">
Filter anwenden
</ui-spinner>
</button>
</div>
</shell-filter-overlay>
<ng-template #noResults>
<div class="bg-white text-2xl text-center pt-10 font-bold rounded-b h-[calc(100vh_-_370px)]">Keine Preisänderungen vorhanden.</div>
</ng-template>

Some files were not shown because too many files have changed in this diff Show More