xv-webcomponents 1.24.10 → 1.24.13-qa.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
 
3
3
  var index$1 = require('./index-DpLAFt0R.js');
4
4
  var index$2 = require('./index-Cb30DBqk.js');
5
- var _vars = require('./_vars-DMytunQw.js');
5
+ var _vars = require('./_vars-Cyrk3TAl.js');
6
6
 
7
7
  const xvAccordionCss = () => `:host{display:block}`;
8
8
 
@@ -82619,9 +82619,15 @@ const XvHeader = class {
82619
82619
  this.metaNav = metaNav;
82620
82620
  this.orgUnit = ((_b = (_a = this.metaNav) === null || _a === void 0 ? void 0 : _a.User) === null || _b === void 0 ? void 0 : _b.SelectedOrgUnitName) || '';
82621
82621
  void this.getNotifications();
82622
+ void this.connectNotificationsHub();
82622
82623
  }
82623
82624
  console.info('MetaNav was updated', this.metaNav);
82624
82625
  }
82626
+ disconnectedCallback() {
82627
+ var _a;
82628
+ void ((_a = this.notificationsHub) === null || _a === void 0 ? void 0 : _a.stop());
82629
+ this.notificationsHub = undefined;
82630
+ }
82625
82631
  componentWillLoad() {
82626
82632
  this.baseUrl = index$2.getBaseUrl();
82627
82633
  void this.initHeader();
@@ -82666,16 +82672,89 @@ const XvHeader = class {
82666
82672
  this.notifications = [];
82667
82673
  }
82668
82674
  }
82675
+ /**
82676
+ * Subscribes to the notifications hub so the bell reflects a status change without a reload.
82677
+ *
82678
+ * The platform calls `NotifyUserAsync` after it completes or fails a notification, which
82679
+ * invokes `NotificationsUpdated` on the person's group — but nothing here was listening, so
82680
+ * the bell only ever caught up on a full page load (MAR-15569). The list is re-fetched on
82681
+ * each push rather than patched from the message, because the hub carries no payload.
82682
+ *
82683
+ * Failure to connect is logged and otherwise ignored: the bell still works, it just needs a
82684
+ * reload to refresh, which is the behaviour this replaces.
82685
+ */
82686
+ async connectNotificationsHub() {
82687
+ var _a, _b, _c;
82688
+ if (!((_a = this.metaNav) === null || _a === void 0 ? void 0 : _a.IsManager) || this.notificationsHub)
82689
+ return;
82690
+ const personId = (_c = (_b = this.metaNav) === null || _b === void 0 ? void 0 : _b.User) === null || _c === void 0 ? void 0 : _c.Id;
82691
+ if (!personId)
82692
+ return;
82693
+ try {
82694
+ const hubUrl = await this.getNotificationsHubUrl();
82695
+ if (!hubUrl)
82696
+ return;
82697
+ const hub = new _vars.HubConnectionBuilder()
82698
+ .withUrl(hubUrl)
82699
+ .withAutomaticReconnect()
82700
+ .build();
82701
+ hub.on(_vars.SIGNALR_NOTIFICATIONS_UPDATED, () => void this.getNotifications());
82702
+ // Re-join after a reconnect: group membership lives on the connection, so it is lost
82703
+ // when the transport drops and the bell would silently stop updating.
82704
+ hub.onreconnected(() => void hub.invoke(_vars.SIGNALR_JOIN_GROUP, String(personId)));
82705
+ await hub.start();
82706
+ await hub.invoke(_vars.SIGNALR_JOIN_GROUP, String(personId));
82707
+ this.notificationsHub = hub;
82708
+ }
82709
+ catch (error) {
82710
+ console.error('Failed to connect to the notifications hub:', error === null || error === void 0 ? void 0 : error.message, error);
82711
+ }
82712
+ }
82713
+ /**
82714
+ * Reads the hub URL from the platform's client settings. That endpoint serves JavaScript
82715
+ * rather than JSON, and the `xv` global it defines is absent on Angular-rendered pages, so
82716
+ * the value is parsed out of the response text.
82717
+ */
82718
+ async getNotificationsHubUrl() {
82719
+ var _a, _b;
82720
+ const fromGlobal = (_b = (_a = window.xv) === null || _a === void 0 ? void 0 : _a.clientApplicationSettings) === null || _b === void 0 ? void 0 : _b[_vars.SIGNALR_HUB_URL_SETTING];
82721
+ if (fromGlobal)
82722
+ return fromGlobal;
82723
+ const settings = await fetch(`${this.baseUrl}/de-de/assets/clientapplicationsettings`, {
82724
+ credentials: 'include',
82725
+ }).then(res => res.text());
82726
+ const match = settings.match(new RegExp(`"${_vars.SIGNALR_HUB_URL_SETTING.replace(/\./g, '\\.')}"\\s*:\\s*"([^"]+)"`));
82727
+ return match === null || match === void 0 ? void 0 : match[1];
82728
+ }
82729
+ /**
82730
+ * Dismisses a notification. The platform soft-deletes it (`DateDeletedUtc`), which drops it
82731
+ * from the list endpoint. Without this the bell could not be cleared at all and every entry
82732
+ * sat there until its 24-hour expiry — the old MVC bell's "Geprüft" button did this and was
82733
+ * not carried over when the header moved to this component.
82734
+ */
82735
+ async dismissNotification(event, orderId) {
82736
+ // The row is inside an overflow-menu item; without this the menu closes on click and the
82737
+ // list is torn down before the request resolves.
82738
+ event.preventDefault();
82739
+ event.stopPropagation();
82740
+ try {
82741
+ await index$2.api.delete(`${this.baseUrl}/de-de/api/notifications/${orderId}`, {
82742
+ credentials: 'include',
82743
+ });
82744
+ }
82745
+ catch (error) {
82746
+ console.error('Failed to dismiss the notification:', error === null || error === void 0 ? void 0 : error.message, error);
82747
+ }
82748
+ await this.getNotifications();
82749
+ }
82669
82750
  _renderNotifications() {
82670
82751
  var _a;
82671
82752
  if (!((_a = this.metaNav) === null || _a === void 0 ? void 0 : _a.IsManager))
82672
82753
  return null;
82673
- return (index$1.h("li", { class: "notifications" }, index$1.h("xv-overflow-menu-v2", { position: "right", size: _vars.SIZE_VAR.MD }, index$1.h("span", { slot: "trigger", class: "fa-stack" }, index$1.h("i", { class: "notifications-icon far fa-bell fa-stack-1x" }), this.notifications.length > 0 && (index$1.h("xv-tag-v2", { class: "counter-badge counter-badge--absolute", bg: "var(--icon-interactive, #97BF0D)", color: "var(--text-inverse, #FFF)" }, this.notifications.length))), this.notifications.map((notification) => (index$1.h("xv-overflow-menu-v2-item", { value: notification.id, variant: _vars.OverflowMenuVariant.GHOST, disabled: notification.isRead, clickable: false }, index$1.h("div", { class: "notifications-item" }, index$1.h("i", { class: {
82674
- far: true,
82675
- 'fa-clock': !notification.status,
82676
- 'fa-times-circle': notification.status === 1,
82677
- 'fa-circle': notification.status && notification.status !== 1,
82678
- } }), index$1.h("div", { class: "notifications-item-body" }, index$1.h("span", null, "Rechnungsentwurf f\u00FCr Auftrag ", notification.orderId, " ist ausstehend"), index$1.h("xv-link-v2", { variant: notification.isRead ? 'ghost' : 'standalone', href: `/de-de/manager/ordersales/details/${notification.orderId}` }, "Auftrag anschauen")))))))));
82754
+ return (index$1.h("li", { class: "notifications" }, index$1.h("xv-overflow-menu-v2", { position: "right", size: _vars.SIZE_VAR.MD }, index$1.h("span", { slot: "trigger", class: "fa-stack" }, index$1.h("i", { class: "notifications-icon far fa-bell fa-stack-1x" }), this.notifications.length > 0 && (index$1.h("xv-tag-v2", { class: "counter-badge counter-badge--absolute", bg: "var(--icon-interactive, #97BF0D)", color: "var(--text-inverse, #FFF)" }, this.notifications.length))), this.notifications.map((notification) => {
82755
+ const status = _vars.invoiceNotificationStatusOf(notification.status);
82756
+ return (index$1.h("xv-overflow-menu-v2-item", { value: notification.id, variant: _vars.OverflowMenuVariant.GHOST, disabled: notification.isRead, clickable: false }, index$1.h("div", { class: "notifications-item" }, index$1.h("i", { class: { far: true, [_vars.INVOICE_NOTIFICATION_STATUS_ICON[status]]: true } }), index$1.h("div", { class: "notifications-item-body" }, index$1.h("span", null, "Rechnungsentwurf f\u00FCr Auftrag ", notification.orderId, " ist ", _vars.INVOICE_NOTIFICATION_STATUS_TEXT[status]), status === _vars.InvoiceNotificationStatus.COMPLETED && notification.pdfSrc ? (index$1.h("xv-link-v2", { variant: notification.isRead ? 'ghost' : 'standalone', href: notification.pdfSrc, target: "_blank" }, "Rechnung ansehen")) : (index$1.h("xv-link-v2", { variant: notification.isRead ? 'ghost' : 'standalone', href: `/de-de/manager/ordersales/details/${notification.orderId}` }, "Auftrag anschauen"))), index$1.h("xv-button-v2", { variant: "ghost", size: _vars.SIZE_VAR.SM, title: "Gepr\u00FCft", "aria-label": `Benachrichtigung für Auftrag ${notification.orderId} als geprüft markieren`, onClick: (event) => void this.dismissNotification(event, notification.orderId) }, index$1.h("i", { class: "far fa-times" })))));
82757
+ }))));
82679
82758
  }
82680
82759
  _renderAccount() {
82681
82760
  var _a, _b, _c, _d, _e, _f, _g, _h;
@@ -82700,7 +82779,7 @@ const XvHeader = class {
82700
82779
  render() {
82701
82780
  var _a, _b, _c, _d, _e;
82702
82781
  const isXvBrand = (((_a = this.metaNav) === null || _a === void 0 ? void 0 : _a['WhitelabelName']) || 'Xv') === 'Xv';
82703
- return (index$1.h("header", { key: '9477fea03faf7b1eee3a59be46a6b6efffabe7ae', class: "header" }, index$1.h("div", { key: '974c597e788966d68bbc0af46ef1c56e5d49c291', class: "header-wrapper" }, index$1.h("div", { key: 'c4efe27850b74ccaf75864abbee66e04fdd39aba', class: "xv-container" }, index$1.h("div", { key: '0dc3df2ffc4dbae8caeb996cbd6bd1293c51126f', class: "header-top" }, index$1.h("div", { key: 'fc2ba21ef07a79bf88aa94049222f7ad31842509', class: "header-top--left" }, index$1.h("xv-button-v2", { key: '13fc5ad1a0b7587ee223a55c5a6f5b5490935c76', class: "menu-btn", variant: "ghost", onClick: this.toggleMobMenu }, index$1.h("i", { key: 'f8baece2d445514f1e30083d109e9a547d84aa9a', slot: "icon-left", class: { 'fa': true, 'fa-times': this.mobMenuOpen, 'fa-bars': !this.mobMenuOpen } })), this.metaNav && (index$1.h("a", { key: '506f618928c24e1a8d804e1b362fdc8fdd6662e7', href: `${window.location.origin}/` }, index$1.h("picture", { key: '4c0ddbce5bac9bdb282bbff733e9bb781dd47645' }, index$1.h("source", { key: '3083b3d605b1c2d30f5dc1935cf1e298b93c5c19', srcSet: ((_b = this.metaNav) === null || _b === void 0 ? void 0 : _b.LogoUrl) || 'https://www.crossvertise.com/ResourcePackages/Bootstrap/assets/dist/images/logo.svg', media: "(min-width: 990px)" }), index$1.h("img", { key: '96f2386509b36fd81954819936154751f042fce0', src: ((_c = this.metaNav) === null || _c === void 0 ? void 0 : _c.LogoSmallUrl) || 'https://www.crossvertise.com/ResourcePackages/Bootstrap/assets/dist/images/logo-small.svg', onError: this.logoFallback, loading: "lazy", alt: "Crossvertise" })))), this.orgUnit && (index$1.h("xv-link-v2", { key: '3bb46d9af623db9e54c2c51630a83ce4e384b479', class: "header-top--orgUnit", variant: "standalone", href: "/de-de/mycrossvertise" }, this.orgUnit))), index$1.h("ul", { key: '36e14a1481748def91b5e76f82631e76d35ae49c', class: "header-top--right" }, this._renderSearch(), this._renderImpersonationInfo(), !((_d = this.metaNav) === null || _d === void 0 ? void 0 : _d.IsManager) && isXvBrand && (index$1.h("li", { key: '07c00dae34dfd6f177ff3803f88755339422eec4' }, index$1.h("xv-link-v2", { key: '573110e4de76b5a9aab3bb5c21519b68a4236de7', variant: "ghost", href: "https://www.crossvertise.com/ueber-uns/kontakt" }, index$1.h("xv-button-v2", { key: '318e47e08a44e34bf10a38d8c2901a2d64bfcf2b', variant: "ghost", size: _vars.SIZE_VAR.MD }, index$1.h("i", { key: 'd44b57c9ccd07db75c19dae59123c0320241cb3e', class: "header-icon far fa-envelope" }), index$1.h("span", { key: 'c9e66d1395ab5b690a2e673994a137807232a084', class: "header-label xv-body-02" }, "Kontakt"))))), this._renderNotifications(), this._renderAccount(), !((_e = this.metaNav) === null || _e === void 0 ? void 0 : _e.IsManager) && isXvBrand && (index$1.h("li", { key: 'ca2f1556456c788b78c4929f2d64e7c7f239c4fe' }, index$1.h("xv-link-v2", { key: '47f0a74790fb2331a404d515e5fbe8369458146c', variant: "ghost", href: "https://help.crossvertise.com" }, index$1.h("xv-button-v2", { key: 'ae850b8f6d7c1da9ea3ba704adb1d670720dff7c', variant: "ghost", size: _vars.SIZE_VAR.MD }, index$1.h("i", { key: '8c8a857e172f937d8eba8083a63e2ff8662e06cb', class: "header-icon far fa-question-circle" }), index$1.h("span", { key: 'bb86a87d43293d313654440a126c4e8ea5d228c9', class: "header-label xv-body-02" }, "Hilfe"))))), this._renderShoppingCart()))), index$1.h("div", { key: '4b0386ef89ed643cd42b6232b10064d6bc73beab', class: "header-wrapper--bg" }, index$1.h("div", { key: '83ef95df9b7820eae211b958f6f1bf57d046905b', class: "xv-container" }, index$1.h("xv-mega-menu", { key: '763117fc0da850b8b1bbea389dcaa3cfacef4847', open: this.mobMenuOpen, onOpenChange: this.closeMobMenu })))), index$1.h("xv-login-modal", { key: 'b7252b1eb7527f6dbab4a1cca972d47b75a66b14', visible: this.showLoginModal, onClose: () => this.showLoginModal = false })));
82782
+ return (index$1.h("header", { key: 'e9157c2348f2e286e05e7b694d692ed88c80a543', class: "header" }, index$1.h("div", { key: 'ce7a4aa5e9820598eff8331b8f9ae6f57cc2c034', class: "header-wrapper" }, index$1.h("div", { key: '7bbec18df899b1234e8409b0c3723719180057ba', class: "xv-container" }, index$1.h("div", { key: 'f733864d082f8d23f69cb57961bdffeea60c7657', class: "header-top" }, index$1.h("div", { key: '63df8d29d16e79ef15cf5c5733a2f9b7662a990e', class: "header-top--left" }, index$1.h("xv-button-v2", { key: '5a782cdff61ce2fb4b9a5685d93d5c36a6a0fc9e', class: "menu-btn", variant: "ghost", onClick: this.toggleMobMenu }, index$1.h("i", { key: '2388b08c95773c3b08eeadf5acbe101119cc996d', slot: "icon-left", class: { 'fa': true, 'fa-times': this.mobMenuOpen, 'fa-bars': !this.mobMenuOpen } })), this.metaNav && (index$1.h("a", { key: '277a9e5179a10208e09b0896e234fd6abac4e6d4', href: `${window.location.origin}/` }, index$1.h("picture", { key: 'f07378b064d3af36a6f3059fd1665544eecd17be' }, index$1.h("source", { key: '719e3c4b4147df467f36c45ba91457c3b7724a35', srcSet: ((_b = this.metaNav) === null || _b === void 0 ? void 0 : _b.LogoUrl) || 'https://www.crossvertise.com/ResourcePackages/Bootstrap/assets/dist/images/logo.svg', media: "(min-width: 990px)" }), index$1.h("img", { key: 'cd5a8c6bcd09d0254b3518410efe1b45b9922b97', src: ((_c = this.metaNav) === null || _c === void 0 ? void 0 : _c.LogoSmallUrl) || 'https://www.crossvertise.com/ResourcePackages/Bootstrap/assets/dist/images/logo-small.svg', onError: this.logoFallback, loading: "lazy", alt: "Crossvertise" })))), this.orgUnit && (index$1.h("xv-link-v2", { key: '4775620b83865d75a8fdb1201ceca5bbe543ad67', class: "header-top--orgUnit", variant: "standalone", href: "/de-de/mycrossvertise" }, this.orgUnit))), index$1.h("ul", { key: 'c3b86b2b97c3fd04fe471c5d2ae49d2b7fc03d27', class: "header-top--right" }, this._renderSearch(), this._renderImpersonationInfo(), !((_d = this.metaNav) === null || _d === void 0 ? void 0 : _d.IsManager) && isXvBrand && (index$1.h("li", { key: '9bfbc4f0ba394fc1b36dbb62f86e334cb06257b3' }, index$1.h("xv-link-v2", { key: 'bca968a2fe64bf6872d43d8ac7f10d78b089a23a', variant: "ghost", href: "https://www.crossvertise.com/ueber-uns/kontakt" }, index$1.h("xv-button-v2", { key: '0db4cf67178bb86beefe8dddf7b378171e15198d', variant: "ghost", size: _vars.SIZE_VAR.MD }, index$1.h("i", { key: 'ed1b26d33fc9b843a0c89b05a4bb609273fa8ce8', class: "header-icon far fa-envelope" }), index$1.h("span", { key: '4e8f6fea781c552338e248b111a24ad289526d46', class: "header-label xv-body-02" }, "Kontakt"))))), this._renderNotifications(), this._renderAccount(), !((_e = this.metaNav) === null || _e === void 0 ? void 0 : _e.IsManager) && isXvBrand && (index$1.h("li", { key: '23f641be3c9218dac34b8abe6b2cbb263c856843' }, index$1.h("xv-link-v2", { key: 'b4315fa914b8173f92092aeb3735fabbd5986d25', variant: "ghost", href: "https://help.crossvertise.com" }, index$1.h("xv-button-v2", { key: '0bec228884977d7c88549a24fe4e62f76a11753b', variant: "ghost", size: _vars.SIZE_VAR.MD }, index$1.h("i", { key: 'd23ccaed77af7631742f23c053ff718278012d4e', class: "header-icon far fa-question-circle" }), index$1.h("span", { key: '0771508d8bdffa99d1e4748141e085191c421bf7', class: "header-label xv-body-02" }, "Hilfe"))))), this._renderShoppingCart()))), index$1.h("div", { key: '97bf836216e1ac3f1d8b0b4ed96b7e0875f6cdcc', class: "header-wrapper--bg" }, index$1.h("div", { key: '9d542f3553673d6c5c27a74b81d899b56562e724', class: "xv-container" }, index$1.h("xv-mega-menu", { key: 'b8b0af38f25546dd3d48a76a4fd7e381982e482e', open: this.mobMenuOpen, onOpenChange: this.closeMobMenu })))), index$1.h("xv-login-modal", { key: 'cad3944d560a74e29399f54c5e47b993a7e7dbe4', visible: this.showLoginModal, onClose: () => this.showLoginModal = false })));
82704
82783
  }
82705
82784
  get el() { return index$1.getElement(this); }
82706
82785
  };
@@ -83420,6 +83499,13 @@ const XvOverflowMenu = class {
83420
83499
  const button = (index$1.h("button", { class: { btn: true, open: this.open }, onClick: this.onOpenToggle, disabled: this.disabled, "aria-haspopup": "menu", "aria-expanded": this.open ? 'true' : 'false', ref: (el) => (this.triggerButtonEl = el) }, index$1.h("slot", { name: "trigger" }, index$1.h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "1em", height: "1em", viewBox: "0 0 128 512" }, index$1.h("path", { d: "M64 368a48 48 0 1 0 0 96 48 48 0 1 0 0-96zm0-160a48 48 0 1 0 0 96 48 48 0 1 0 0-96zM112 96A48 48 0 1 0 16 96a48 48 0 1 0 96 0z" })))));
83421
83500
  return (index$1.h(index$1.Host, { class: "xv-overflow-menu", size: this.size }, this.tooltip ? index$1.h("xv-tooltip-v2", { message: this.tooltip }, button) : button, list));
83422
83501
  }
83502
+ // Moving the host in the DOM — a framework reordering a list, say — disconnects and reconnects
83503
+ // it, and the disconnect tears the anchor listener down. componentDidLoad does not run a second
83504
+ // time, so without this the menu stays dead. Safe on the first mount: addEventListener discards
83505
+ // a duplicate of the same handler, and setupAnchor is a no-op until anchor is set.
83506
+ connectedCallback() {
83507
+ this.setupAnchor();
83508
+ }
83423
83509
  componentDidLoad() {
83424
83510
  this.setupAnchor();
83425
83511
  this.syncOpenState();
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var index = require('./index-DpLAFt0R.js');
4
- var _vars = require('./_vars-DMytunQw.js');
4
+ var _vars = require('./_vars-Cyrk3TAl.js');
5
5
  var index$1 = require('./index-Cb30DBqk.js');
6
6
 
7
7
  const userMagnifyingGlassRegularFullSvg = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NDAgNjQwIj48IS0tIUZvbnQgQXdlc29tZSBQcm8gNy4xLjAgYnkgQGZvbnRhd2Vzb21lIC0gaHR0cHM6Ly9mb250YXdlc29tZS5jb20gTGljZW5zZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tL2xpY2Vuc2UgKENvbW1lcmNpYWwgTGljZW5zZSkgQ29weXJpZ2h0IDIwMjUgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTE3Ni41IDE5MkMxNzYuNSAxNDcuOCAyMTIuMyAxMTIgMjU2LjUgMTEyQzMwMC43IDExMiAzMzYuNSAxNDcuOCAzMzYuNSAxOTJDMzM2LjUgMjM2LjIgMzAwLjcgMjcyIDI1Ni41IDI3MkMyMTIuMyAyNzIgMTc2LjUgMjM2LjIgMTc2LjUgMTkyek0zODQuNSAxOTJDMzg0LjUgMTIxLjMgMzI3LjIgNjQgMjU2LjUgNjRDMTg1LjggNjQgMTI4LjUgMTIxLjMgMTI4LjUgMTkyQzEyOC41IDI2Mi43IDE4NS44IDMyMCAyNTYuNSAzMjBDMzI3LjIgMzIwIDM4NC41IDI2Mi43IDM4NC41IDE5MnpNODAuNSA1NDRDODAuNSA0NzMuMyAxMzcuOCA0MTYgMjA4LjUgNDE2TDI4OC43IDQxNkMyODkuNSAzOTkuMiAyOTIuNyAzODMuMSAyOTguMSAzNjhMMjA4LjUgMzY4QzExMS4zIDM2OCAzMi41IDQ0Ni44IDMyLjUgNTQ0TDMyLjUgNTUyQzMyLjUgNTY1LjMgNDMuMiA1NzYgNTYuNSA1NzZDNjkuOCA1NzYgODAuNSA1NjUuMyA4MC41IDU1Mkw4MC41IDU0NHpNNDU2LjUgMzUyQzQ5Ni4zIDM1MiA1MjguNSAzODQuMiA1MjguNSA0MjRDNTI4LjUgNDYzLjggNDk2LjMgNDk2IDQ1Ni41IDQ5NkM0MTYuNyA0OTYgMzg0LjUgNDYzLjggMzg0LjUgNDI0QzM4NC41IDM4NC4yIDQxNi43IDM1MiA0NTYuNSAzNTJ6TTQ1Ni41IDU0NEM0ODAuOSA1NDQgNTAzLjYgNTM2LjcgNTIyLjYgNTI0LjJMNTgzLjUgNTg1LjFDNTkyLjkgNTk0LjUgNjA4LjEgNTk0LjUgNjE3LjQgNTg1LjFDNjI2LjcgNTc1LjcgNjI2LjggNTYwLjUgNjE3LjQgNTUxLjJMNTU2LjUgNDkwLjNDNTY5LjEgNDcxLjMgNTc2LjQgNDQ4LjUgNTc2LjQgNDI0QzU3Ni40IDM1Ny43IDUyMi43IDMwNCA0NTYuNCAzMDRDMzkwLjEgMzA0IDMzNi40IDM1Ny43IDMzNi40IDQyNEMzMzYuNCA0OTAuMyAzOTAuMSA1NDQgNDU2LjQgNTQ0eiIvPjwvc3ZnPg==';
@@ -99,9 +99,15 @@ const XvHeaderNext = class {
99
99
  this.metaNav = metaNav;
100
100
  this.orgUnit = ((_b = (_a = this.metaNav) === null || _a === void 0 ? void 0 : _a.User) === null || _b === void 0 ? void 0 : _b.SelectedOrgUnitName) || '';
101
101
  void this.getNotifications();
102
+ void this.connectNotificationsHub();
102
103
  }
103
104
  console.info('MetaNav was updated', this.metaNav);
104
105
  }
106
+ disconnectedCallback() {
107
+ var _a;
108
+ void ((_a = this.notificationsHub) === null || _a === void 0 ? void 0 : _a.stop());
109
+ this.notificationsHub = undefined;
110
+ }
105
111
  componentWillLoad() {
106
112
  this.baseUrl = index$1.getBaseUrl();
107
113
  void this.initHeader();
@@ -171,16 +177,89 @@ const XvHeaderNext = class {
171
177
  this.notifications = [];
172
178
  }
173
179
  }
180
+ /**
181
+ * Subscribes to the notifications hub so the bell reflects a status change without a reload.
182
+ *
183
+ * The platform calls `NotifyUserAsync` after it completes or fails a notification, which
184
+ * invokes `NotificationsUpdated` on the person's group — but nothing here was listening, so
185
+ * the bell only ever caught up on a full page load (MAR-15569). The list is re-fetched on
186
+ * each push rather than patched from the message, because the hub carries no payload.
187
+ *
188
+ * Failure to connect is logged and otherwise ignored: the bell still works, it just needs a
189
+ * reload to refresh, which is the behaviour this replaces.
190
+ */
191
+ async connectNotificationsHub() {
192
+ var _a, _b, _c;
193
+ if (!((_a = this.metaNav) === null || _a === void 0 ? void 0 : _a.IsManager) || this.notificationsHub)
194
+ return;
195
+ const personId = (_c = (_b = this.metaNav) === null || _b === void 0 ? void 0 : _b.User) === null || _c === void 0 ? void 0 : _c.Id;
196
+ if (!personId)
197
+ return;
198
+ try {
199
+ const hubUrl = await this.getNotificationsHubUrl();
200
+ if (!hubUrl)
201
+ return;
202
+ const hub = new _vars.HubConnectionBuilder()
203
+ .withUrl(hubUrl)
204
+ .withAutomaticReconnect()
205
+ .build();
206
+ hub.on(_vars.SIGNALR_NOTIFICATIONS_UPDATED, () => void this.getNotifications());
207
+ // Re-join after a reconnect: group membership lives on the connection, so it is lost
208
+ // when the transport drops and the bell would silently stop updating.
209
+ hub.onreconnected(() => void hub.invoke(_vars.SIGNALR_JOIN_GROUP, String(personId)));
210
+ await hub.start();
211
+ await hub.invoke(_vars.SIGNALR_JOIN_GROUP, String(personId));
212
+ this.notificationsHub = hub;
213
+ }
214
+ catch (error) {
215
+ console.error('Failed to connect to the notifications hub:', error === null || error === void 0 ? void 0 : error.message, error);
216
+ }
217
+ }
218
+ /**
219
+ * Reads the hub URL from the platform's client settings. That endpoint serves JavaScript
220
+ * rather than JSON, and the `xv` global it defines is absent on Angular-rendered pages, so
221
+ * the value is parsed out of the response text.
222
+ */
223
+ async getNotificationsHubUrl() {
224
+ var _a, _b;
225
+ const fromGlobal = (_b = (_a = window.xv) === null || _a === void 0 ? void 0 : _a.clientApplicationSettings) === null || _b === void 0 ? void 0 : _b[_vars.SIGNALR_HUB_URL_SETTING];
226
+ if (fromGlobal)
227
+ return fromGlobal;
228
+ const settings = await fetch(`${this.baseUrl}/de-de/assets/clientapplicationsettings`, {
229
+ credentials: 'include',
230
+ }).then(res => res.text());
231
+ const match = settings.match(new RegExp(`"${_vars.SIGNALR_HUB_URL_SETTING.replace(/\./g, '\\.')}"\\s*:\\s*"([^"]+)"`));
232
+ return match === null || match === void 0 ? void 0 : match[1];
233
+ }
234
+ /**
235
+ * Dismisses a notification. The platform soft-deletes it (`DateDeletedUtc`), which drops it
236
+ * from the list endpoint. Without this the bell could not be cleared at all and every entry
237
+ * sat there until its 24-hour expiry — the old MVC bell's "Geprüft" button did this and was
238
+ * not carried over when the header moved to this component.
239
+ */
240
+ async dismissNotification(event, orderId) {
241
+ // The row is inside an overflow-menu item; without this the menu closes on click and the
242
+ // list is torn down before the request resolves.
243
+ event.preventDefault();
244
+ event.stopPropagation();
245
+ try {
246
+ await index$1.api.delete(`${this.baseUrl}/de-de/api/notifications/${orderId}`, {
247
+ credentials: 'include',
248
+ });
249
+ }
250
+ catch (error) {
251
+ console.error('Failed to dismiss the notification:', error === null || error === void 0 ? void 0 : error.message, error);
252
+ }
253
+ await this.getNotifications();
254
+ }
174
255
  _renderNotifications() {
175
256
  var _a;
176
257
  if (!((_a = this.metaNav) === null || _a === void 0 ? void 0 : _a.IsManager))
177
258
  return null;
178
- return (index.h("li", { class: "notifications" }, index.h("xv-overflow-menu-v2", { position: "right", size: _vars.SIZE_VAR.MD }, index.h("span", { slot: "trigger", class: "fa-stack" }, index.h("i", { class: "notifications-icon far fa-bell fa-stack-1x" }), this.notifications.length > 0 && (index.h("xv-tag-v2", { class: "counter-badge counter-badge--absolute", bg: "var(--icon-interactive, #97BF0D)", color: "var(--text-inverse, #FFF)" }, this.notifications.length))), this.notifications.map((notification) => (index.h("xv-overflow-menu-v2-item", { value: notification.id, variant: _vars.OverflowMenuVariant.GHOST, disabled: notification.isRead, clickable: false }, index.h("div", { class: "notifications-item" }, index.h("i", { class: {
179
- far: true,
180
- 'fa-clock': !notification.status,
181
- 'fa-times-circle': notification.status === 1,
182
- 'fa-circle': notification.status && notification.status !== 1,
183
- } }), index.h("div", { class: "notifications-item-body" }, index.h("span", null, "Rechnungsentwurf f\u00FCr Auftrag ", notification.orderId, " ist ausstehend"), index.h("xv-link-v2", { variant: notification.isRead ? 'ghost' : 'standalone', href: `/de-de/manager/ordersales/details/${notification.orderId}` }, "Auftrag anschauen")))))))));
259
+ return (index.h("li", { class: "notifications" }, index.h("xv-overflow-menu-v2", { position: "right", size: _vars.SIZE_VAR.MD }, index.h("span", { slot: "trigger", class: "fa-stack" }, index.h("i", { class: "notifications-icon far fa-bell fa-stack-1x" }), this.notifications.length > 0 && (index.h("xv-tag-v2", { class: "counter-badge counter-badge--absolute", bg: "var(--icon-interactive, #97BF0D)", color: "var(--text-inverse, #FFF)" }, this.notifications.length))), this.notifications.map((notification) => {
260
+ const status = _vars.invoiceNotificationStatusOf(notification.status);
261
+ return (index.h("xv-overflow-menu-v2-item", { value: notification.id, variant: _vars.OverflowMenuVariant.GHOST, disabled: notification.isRead, clickable: false }, index.h("div", { class: "notifications-item" }, index.h("i", { class: { far: true, [_vars.INVOICE_NOTIFICATION_STATUS_ICON[status]]: true } }), index.h("div", { class: "notifications-item-body" }, index.h("span", null, "Rechnungsentwurf f\u00FCr Auftrag ", notification.orderId, " ist ", _vars.INVOICE_NOTIFICATION_STATUS_TEXT[status]), status === _vars.InvoiceNotificationStatus.COMPLETED && notification.pdfSrc ? (index.h("xv-link-v2", { variant: notification.isRead ? 'ghost' : 'standalone', href: notification.pdfSrc, target: "_blank" }, "Rechnung ansehen")) : (index.h("xv-link-v2", { variant: notification.isRead ? 'ghost' : 'standalone', href: `/de-de/manager/ordersales/details/${notification.orderId}` }, "Auftrag anschauen"))), index.h("xv-button-v2", { variant: "ghost", size: _vars.SIZE_VAR.SM, title: "Gepr\u00FCft", "aria-label": `Benachrichtigung für Auftrag ${notification.orderId} als geprüft markieren`, onClick: (event) => void this.dismissNotification(event, notification.orderId) }, index.h("i", { class: "far fa-times" })))));
262
+ }))));
184
263
  }
185
264
  _renderAccount() {
186
265
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
@@ -199,7 +278,7 @@ const XvHeaderNext = class {
199
278
  render() {
200
279
  var _a, _b, _c, _d, _e;
201
280
  const isXvBrand = (((_a = this.metaNav) === null || _a === void 0 ? void 0 : _a['WhitelabelName']) || 'Xv') === 'Xv';
202
- return (index.h("header", { key: '19af5ee68042a36f7217b0b35a24a759fc4847db', class: "header" }, index.h("div", { key: '197dcbf9022f303f0f247cb5766d2e58dec60444', class: "header-wrapper" }, index.h("div", { key: 'ff984cbcd0294ae4d63c0c874d7788501b1e7e4a', class: "xv-container" }, index.h("div", { key: '92727fd92023f03c6e099321a5516ac07df40dff', class: "header-top" }, index.h("div", { key: '66209080f19179d302dcbe1c849c099b67067aa2', class: "header-top--left" }, index.h("xv-button-v2", { key: 'e0555bd0186c9ab16a67d99bb39a54d0a5bfa1c7', class: "menu-btn", variant: "ghost", onClick: this.toggleMobMenu }, index.h("i", { key: '444dfb14fe820289f909bcd225b67b9728ebee00', slot: "icon-left", class: { 'fa': true, 'fa-times': this.mobMenuOpen, 'fa-bars': !this.mobMenuOpen } })), this.metaNav && (index.h("a", { key: '363aa6b89fc901423d3e086b91bf0e87090aad77', href: `${window.location.origin}/` }, index.h("picture", { key: '10d0f780f4b3c8d82ddc42cc4bc3bcdfed3f6bb3' }, index.h("source", { key: '4f380ce8f4a2e0a49040b64b58c8ccde1b58c922', srcSet: ((_b = this.metaNav) === null || _b === void 0 ? void 0 : _b.LogoUrl) || 'https://www.crossvertise.com/ResourcePackages/Bootstrap/assets/dist/images/logo.svg', media: "(min-width: 990px)" }), index.h("img", { key: 'cddd6551b4610fb701d61dbe9b201b4e65f39f0d', src: ((_c = this.metaNav) === null || _c === void 0 ? void 0 : _c.LogoSmallUrl) || 'https://www.crossvertise.com/ResourcePackages/Bootstrap/assets/dist/images/logo-small.svg', onError: this.logoFallback, loading: "lazy", alt: "Crossvertise" })))), this.orgUnit && (index.h("xv-link-v2", { key: '5e2cde3553edb563b91eab3c5b36d6a9677c1c19', class: "header-top--orgUnit", variant: "standalone", href: "/de-de/mycrossvertise" }, this.orgUnit))), index.h("ul", { key: '19e47020fc57304c06ebe71182ed9c8308571fad', class: "header-top--right" }, this._renderLocaleSwitcher(), this._renderSearch(), this._renderImpersonationInfo(), !((_d = this.metaNav) === null || _d === void 0 ? void 0 : _d.IsManager) && isXvBrand && (index.h("li", { key: '5bc27068226b038fd783328ec595bf78c6f3cfbd' }, index.h("xv-link-v2", { key: 'd9d87362899586ccd41ef88a9fc8a111c9fe1653', variant: "ghost", href: "https://www.crossvertise.com/ueber-uns/kontakt" }, index.h("xv-button-v2", { key: '72a6d1e98efefd56ad8582b1e6b5209c081b26f1', variant: "ghost", size: _vars.SIZE_VAR.MD }, index.h("i", { key: 'c66c01eaabe1a295641813585681222162327c9f', class: "header-icon far fa-envelope" }), index.h("span", { key: 'ebdf99544154ecc3f04853decd776f0558cba660', class: "header-label xv-body-02" }, "Kontakt"))))), this._renderNotifications(), this._renderAccount(), !((_e = this.metaNav) === null || _e === void 0 ? void 0 : _e.IsManager) && isXvBrand && (index.h("li", { key: '288484baf69d63ff0f38e027294667ff0050dfe1' }, index.h("xv-link-v2", { key: '3759c87928c3c1e145dba55cdb73364ec4424cc9', variant: "ghost", href: "https://help.crossvertise.com" }, index.h("xv-button-v2", { key: '4d726ccab98a688a771925a9f10f5a02f79e68a5', variant: "ghost", size: _vars.SIZE_VAR.MD }, index.h("i", { key: '79d42dad22db9570d278db4fab32d6869a19c536', class: "header-icon far fa-question-circle" }), index.h("span", { key: '009a192511b8b6c19c461a94d772d1e6c4c39d56', class: "header-label xv-body-02" }, "Hilfe"))))), this._renderShoppingCart()))), index.h("div", { key: '31a71c085c02e36cbfe35412357cceff25cb752b', class: "header-wrapper--bg" }, index.h("div", { key: '9041f088f54852a0d144b01ecb2f730dc4b00e50', class: "xv-container" }, index.h("xv-mega-menu-next", { key: '6b8542f8962437488256b01611de616c391a9d94', open: this.mobMenuOpen, onOpenChange: this.closeMobMenu, menuData: this.menuData })))), index.h("xv-login-modal", { key: 'c6cb5f0f604d3e71bf22edafbbaa9313076d5b28', visible: this.showLoginModal, onClose: () => this.showLoginModal = false })));
281
+ return (index.h("header", { key: '9c04fce6bf5c86c2f7ce9eec4f2c0ebbb5941e1a', class: "header" }, index.h("div", { key: 'fee93197d318b26dbca661595916d9e8cbe94375', class: "header-wrapper" }, index.h("div", { key: 'e5ba7c77f93cfd10ac648125e3785572b1b2b352', class: "xv-container" }, index.h("div", { key: '784da35e8aa09bab4268377d22d7ae40aa7fcc5a', class: "header-top" }, index.h("div", { key: '93bffa009121bd0ef93d1c690bed6b90dbb9de4b', class: "header-top--left" }, index.h("xv-button-v2", { key: 'f60f995957e9b929516d30e8c79e6e69354fa4a3', class: "menu-btn", variant: "ghost", onClick: this.toggleMobMenu }, index.h("i", { key: 'd0f2b955f98752b4825b921f57858bba1bdac2cd', slot: "icon-left", class: { 'fa': true, 'fa-times': this.mobMenuOpen, 'fa-bars': !this.mobMenuOpen } })), this.metaNav && (index.h("a", { key: 'd393b761eb12268735da317e85cbb7845c9ee15f', href: `${window.location.origin}/` }, index.h("picture", { key: 'ebc31771f501886f73c033cd8df3ed3f6097dbea' }, index.h("source", { key: '1852235f967ab2fa08d14837d8e2f19607b2da05', srcSet: ((_b = this.metaNav) === null || _b === void 0 ? void 0 : _b.LogoUrl) || 'https://www.crossvertise.com/ResourcePackages/Bootstrap/assets/dist/images/logo.svg', media: "(min-width: 990px)" }), index.h("img", { key: '5d36ae93c924554a4af9e266e090d9ab5cace2d6', src: ((_c = this.metaNav) === null || _c === void 0 ? void 0 : _c.LogoSmallUrl) || 'https://www.crossvertise.com/ResourcePackages/Bootstrap/assets/dist/images/logo-small.svg', onError: this.logoFallback, loading: "lazy", alt: "Crossvertise" })))), this.orgUnit && (index.h("xv-link-v2", { key: '2a674365dbb3d87541fa8ee21e681d30803845f5', class: "header-top--orgUnit", variant: "standalone", href: "/de-de/mycrossvertise" }, this.orgUnit))), index.h("ul", { key: 'c04cc5a6124c47f63aa4a31c6105707fde987f80', class: "header-top--right" }, this._renderLocaleSwitcher(), this._renderSearch(), this._renderImpersonationInfo(), !((_d = this.metaNav) === null || _d === void 0 ? void 0 : _d.IsManager) && isXvBrand && (index.h("li", { key: 'f98924908679cb70e040342e3d2037ca0c328cfb' }, index.h("xv-link-v2", { key: 'df6c95995411827fb9e7d2d772672d71ca94e8b4', variant: "ghost", href: "https://www.crossvertise.com/ueber-uns/kontakt" }, index.h("xv-button-v2", { key: '26fb5b25c5c9d8763ae8162bd027814a511aaeea', variant: "ghost", size: _vars.SIZE_VAR.MD }, index.h("i", { key: '125dcdb6c7732ef43b32cd5600e26b046aa14c62', class: "header-icon far fa-envelope" }), index.h("span", { key: '2a0b974fb77e7aa9eeafec92789bb73f2201c865', class: "header-label xv-body-02" }, "Kontakt"))))), this._renderNotifications(), this._renderAccount(), !((_e = this.metaNav) === null || _e === void 0 ? void 0 : _e.IsManager) && isXvBrand && (index.h("li", { key: 'f0ed40566ca8c76960940a602d8829adc0ede357' }, index.h("xv-link-v2", { key: '5ee23ea00cfba00c759bde207af7385d84e9db6a', variant: "ghost", href: "https://help.crossvertise.com" }, index.h("xv-button-v2", { key: '97129651b7d2726eaf76db5ed4cbc838eaa7e047', variant: "ghost", size: _vars.SIZE_VAR.MD }, index.h("i", { key: 'be2de402a7e605cf600aec63051291d62e497865', class: "header-icon far fa-question-circle" }), index.h("span", { key: '5fe9768447d0d8a8a57fa5a95b98326121eca94b', class: "header-label xv-body-02" }, "Hilfe"))))), this._renderShoppingCart()))), index.h("div", { key: '1e57385c58e31de469d67829e829781065799ca2', class: "header-wrapper--bg" }, index.h("div", { key: '40eaa28197cf1b27b7bd7a6b559440b805764fc6', class: "xv-container" }, index.h("xv-mega-menu-next", { key: '387772fe79f32eef0286fab9f8dd38a2fbb9d6ee', open: this.mobMenuOpen, onOpenChange: this.closeMobMenu, menuData: this.menuData })))), index.h("xv-login-modal", { key: '182eab0fb5f02c082be39f681fc2632881da9e39', visible: this.showLoginModal, onClose: () => this.showLoginModal = false })));
203
282
  }
204
283
  get el() { return index.getElement(this); }
205
284
  };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Status of a draft-invoice notification, mirroring the platform's
3
+ * `InvoiceNotificationPayloadStatus` (Xv.Domain/Notifications/Payloads).
4
+ *
5
+ * The numeric values are the wire format: `/de-de/api/notifications/list` serializes the
6
+ * enum by ordinal, so these MUST stay in sync with the backend. The header used to compare
7
+ * `status === 1` while treating it as a failure, so a draft invoice that had been generated
8
+ * was shown with the error icon and the text "ist ausstehend" (MAR-15569).
9
+ */
10
+ export var InvoiceNotificationStatus;
11
+ (function (InvoiceNotificationStatus) {
12
+ InvoiceNotificationStatus[InvoiceNotificationStatus["PENDING"] = 0] = "PENDING";
13
+ InvoiceNotificationStatus[InvoiceNotificationStatus["COMPLETED"] = 1] = "COMPLETED";
14
+ InvoiceNotificationStatus[InvoiceNotificationStatus["FAILED"] = 2] = "FAILED";
15
+ })(InvoiceNotificationStatus || (InvoiceNotificationStatus = {}));
16
+ /** Bell label per status. German only — this component carries no i18n layer. */
17
+ export const INVOICE_NOTIFICATION_STATUS_TEXT = {
18
+ [InvoiceNotificationStatus.PENDING]: 'ausstehend',
19
+ [InvoiceNotificationStatus.COMPLETED]: 'generiert',
20
+ [InvoiceNotificationStatus.FAILED]: 'fehlgeschlagen',
21
+ };
22
+ /** Font Awesome icon per status. */
23
+ export const INVOICE_NOTIFICATION_STATUS_ICON = {
24
+ [InvoiceNotificationStatus.PENDING]: 'fa-clock',
25
+ [InvoiceNotificationStatus.COMPLETED]: 'fa-check-circle',
26
+ [InvoiceNotificationStatus.FAILED]: 'fa-times-circle',
27
+ };
28
+ /**
29
+ * Falls back to PENDING for an unknown value so a new backend status renders as
30
+ * "waiting" rather than silently claiming success or failure.
31
+ */
32
+ export function invoiceNotificationStatusOf(status) {
33
+ switch (status) {
34
+ case InvoiceNotificationStatus.COMPLETED:
35
+ return InvoiceNotificationStatus.COMPLETED;
36
+ case InvoiceNotificationStatus.FAILED:
37
+ return InvoiceNotificationStatus.FAILED;
38
+ default:
39
+ return InvoiceNotificationStatus.PENDING;
40
+ }
41
+ }
42
+ /**
43
+ * Key of the platform setting holding the notifications hub URL. Served as JavaScript (not
44
+ * JSON) by `/de-de/assets/clientapplicationsettings`, and the `xv` global it defines is not
45
+ * present on Angular-rendered pages, so the value is parsed out of the response text.
46
+ */
47
+ export const SIGNALR_HUB_URL_SETTING = 'SignalR.Hub.Notifications.Url';
48
+ /** Hub method the platform invokes after it changes a notification (see NotificationService). */
49
+ export const SIGNALR_NOTIFICATIONS_UPDATED = 'NotificationsUpdated';
50
+ /** Hub method that subscribes this connection to one person's notifications. */
51
+ export const SIGNALR_JOIN_GROUP = 'joinGroup';
@@ -2,6 +2,8 @@ import { h } from "@stencil/core";
2
2
  import { SIZE_VAR } from "../../types/enum";
3
3
  import { api, debounce, getBaseUrl, getMetaNav } from "../../utils/utils";
4
4
  import { OverflowMenuVariant } from "../xv-overflow-menu/_vars";
5
+ import { HubConnectionBuilder } from "@microsoft/signalr";
6
+ import { INVOICE_NOTIFICATION_STATUS_ICON, INVOICE_NOTIFICATION_STATUS_TEXT, InvoiceNotificationStatus, invoiceNotificationStatusOf, SIGNALR_HUB_URL_SETTING, SIGNALR_JOIN_GROUP, SIGNALR_NOTIFICATIONS_UPDATED, } from "./_vars";
5
7
  import UserSearchIcon from "./user-magnifying-glass-regular-full.svg";
6
8
  export class XvHeader {
7
9
  constructor() {
@@ -92,9 +94,15 @@ export class XvHeader {
92
94
  this.metaNav = metaNav;
93
95
  this.orgUnit = ((_b = (_a = this.metaNav) === null || _a === void 0 ? void 0 : _a.User) === null || _b === void 0 ? void 0 : _b.SelectedOrgUnitName) || '';
94
96
  void this.getNotifications();
97
+ void this.connectNotificationsHub();
95
98
  }
96
99
  console.info('MetaNav was updated', this.metaNav);
97
100
  }
101
+ disconnectedCallback() {
102
+ var _a;
103
+ void ((_a = this.notificationsHub) === null || _a === void 0 ? void 0 : _a.stop());
104
+ this.notificationsHub = undefined;
105
+ }
98
106
  componentWillLoad() {
99
107
  this.baseUrl = getBaseUrl();
100
108
  void this.initHeader();
@@ -139,16 +147,89 @@ export class XvHeader {
139
147
  this.notifications = [];
140
148
  }
141
149
  }
150
+ /**
151
+ * Subscribes to the notifications hub so the bell reflects a status change without a reload.
152
+ *
153
+ * The platform calls `NotifyUserAsync` after it completes or fails a notification, which
154
+ * invokes `NotificationsUpdated` on the person's group — but nothing here was listening, so
155
+ * the bell only ever caught up on a full page load (MAR-15569). The list is re-fetched on
156
+ * each push rather than patched from the message, because the hub carries no payload.
157
+ *
158
+ * Failure to connect is logged and otherwise ignored: the bell still works, it just needs a
159
+ * reload to refresh, which is the behaviour this replaces.
160
+ */
161
+ async connectNotificationsHub() {
162
+ var _a, _b, _c;
163
+ if (!((_a = this.metaNav) === null || _a === void 0 ? void 0 : _a.IsManager) || this.notificationsHub)
164
+ return;
165
+ const personId = (_c = (_b = this.metaNav) === null || _b === void 0 ? void 0 : _b.User) === null || _c === void 0 ? void 0 : _c.Id;
166
+ if (!personId)
167
+ return;
168
+ try {
169
+ const hubUrl = await this.getNotificationsHubUrl();
170
+ if (!hubUrl)
171
+ return;
172
+ const hub = new HubConnectionBuilder()
173
+ .withUrl(hubUrl)
174
+ .withAutomaticReconnect()
175
+ .build();
176
+ hub.on(SIGNALR_NOTIFICATIONS_UPDATED, () => void this.getNotifications());
177
+ // Re-join after a reconnect: group membership lives on the connection, so it is lost
178
+ // when the transport drops and the bell would silently stop updating.
179
+ hub.onreconnected(() => void hub.invoke(SIGNALR_JOIN_GROUP, String(personId)));
180
+ await hub.start();
181
+ await hub.invoke(SIGNALR_JOIN_GROUP, String(personId));
182
+ this.notificationsHub = hub;
183
+ }
184
+ catch (error) {
185
+ console.error('Failed to connect to the notifications hub:', error === null || error === void 0 ? void 0 : error.message, error);
186
+ }
187
+ }
188
+ /**
189
+ * Reads the hub URL from the platform's client settings. That endpoint serves JavaScript
190
+ * rather than JSON, and the `xv` global it defines is absent on Angular-rendered pages, so
191
+ * the value is parsed out of the response text.
192
+ */
193
+ async getNotificationsHubUrl() {
194
+ var _a, _b;
195
+ const fromGlobal = (_b = (_a = window.xv) === null || _a === void 0 ? void 0 : _a.clientApplicationSettings) === null || _b === void 0 ? void 0 : _b[SIGNALR_HUB_URL_SETTING];
196
+ if (fromGlobal)
197
+ return fromGlobal;
198
+ const settings = await fetch(`${this.baseUrl}/de-de/assets/clientapplicationsettings`, {
199
+ credentials: 'include',
200
+ }).then(res => res.text());
201
+ const match = settings.match(new RegExp(`"${SIGNALR_HUB_URL_SETTING.replace(/\./g, '\\.')}"\\s*:\\s*"([^"]+)"`));
202
+ return match === null || match === void 0 ? void 0 : match[1];
203
+ }
204
+ /**
205
+ * Dismisses a notification. The platform soft-deletes it (`DateDeletedUtc`), which drops it
206
+ * from the list endpoint. Without this the bell could not be cleared at all and every entry
207
+ * sat there until its 24-hour expiry — the old MVC bell's "Geprüft" button did this and was
208
+ * not carried over when the header moved to this component.
209
+ */
210
+ async dismissNotification(event, orderId) {
211
+ // The row is inside an overflow-menu item; without this the menu closes on click and the
212
+ // list is torn down before the request resolves.
213
+ event.preventDefault();
214
+ event.stopPropagation();
215
+ try {
216
+ await api.delete(`${this.baseUrl}/de-de/api/notifications/${orderId}`, {
217
+ credentials: 'include',
218
+ });
219
+ }
220
+ catch (error) {
221
+ console.error('Failed to dismiss the notification:', error === null || error === void 0 ? void 0 : error.message, error);
222
+ }
223
+ await this.getNotifications();
224
+ }
142
225
  _renderNotifications() {
143
226
  var _a;
144
227
  if (!((_a = this.metaNav) === null || _a === void 0 ? void 0 : _a.IsManager))
145
228
  return null;
146
- return (h("li", { class: "notifications" }, h("xv-overflow-menu-v2", { position: "right", size: SIZE_VAR.MD }, h("span", { slot: "trigger", class: "fa-stack" }, h("i", { class: "notifications-icon far fa-bell fa-stack-1x" }), this.notifications.length > 0 && (h("xv-tag-v2", { class: "counter-badge counter-badge--absolute", bg: "var(--icon-interactive, #97BF0D)", color: "var(--text-inverse, #FFF)" }, this.notifications.length))), this.notifications.map((notification) => (h("xv-overflow-menu-v2-item", { value: notification.id, variant: OverflowMenuVariant.GHOST, disabled: notification.isRead, clickable: false }, h("div", { class: "notifications-item" }, h("i", { class: {
147
- far: true,
148
- 'fa-clock': !notification.status,
149
- 'fa-times-circle': notification.status === 1,
150
- 'fa-circle': notification.status && notification.status !== 1,
151
- } }), h("div", { class: "notifications-item-body" }, h("span", null, "Rechnungsentwurf f\u00FCr Auftrag ", notification.orderId, " ist ausstehend"), h("xv-link-v2", { variant: notification.isRead ? 'ghost' : 'standalone', href: `/de-de/manager/ordersales/details/${notification.orderId}` }, "Auftrag anschauen")))))))));
229
+ return (h("li", { class: "notifications" }, h("xv-overflow-menu-v2", { position: "right", size: SIZE_VAR.MD }, h("span", { slot: "trigger", class: "fa-stack" }, h("i", { class: "notifications-icon far fa-bell fa-stack-1x" }), this.notifications.length > 0 && (h("xv-tag-v2", { class: "counter-badge counter-badge--absolute", bg: "var(--icon-interactive, #97BF0D)", color: "var(--text-inverse, #FFF)" }, this.notifications.length))), this.notifications.map((notification) => {
230
+ const status = invoiceNotificationStatusOf(notification.status);
231
+ return (h("xv-overflow-menu-v2-item", { value: notification.id, variant: OverflowMenuVariant.GHOST, disabled: notification.isRead, clickable: false }, h("div", { class: "notifications-item" }, h("i", { class: { far: true, [INVOICE_NOTIFICATION_STATUS_ICON[status]]: true } }), h("div", { class: "notifications-item-body" }, h("span", null, "Rechnungsentwurf f\u00FCr Auftrag ", notification.orderId, " ist ", INVOICE_NOTIFICATION_STATUS_TEXT[status]), status === InvoiceNotificationStatus.COMPLETED && notification.pdfSrc ? (h("xv-link-v2", { variant: notification.isRead ? 'ghost' : 'standalone', href: notification.pdfSrc, target: "_blank" }, "Rechnung ansehen")) : (h("xv-link-v2", { variant: notification.isRead ? 'ghost' : 'standalone', href: `/de-de/manager/ordersales/details/${notification.orderId}` }, "Auftrag anschauen"))), h("xv-button-v2", { variant: "ghost", size: SIZE_VAR.SM, title: "Gepr\u00FCft", "aria-label": `Benachrichtigung für Auftrag ${notification.orderId} als geprüft markieren`, onClick: (event) => void this.dismissNotification(event, notification.orderId) }, h("i", { class: "far fa-times" })))));
232
+ }))));
152
233
  }
153
234
  _renderAccount() {
154
235
  var _a, _b, _c, _d, _e, _f, _g, _h;
@@ -173,7 +254,7 @@ export class XvHeader {
173
254
  render() {
174
255
  var _a, _b, _c, _d, _e;
175
256
  const isXvBrand = (((_a = this.metaNav) === null || _a === void 0 ? void 0 : _a['WhitelabelName']) || 'Xv') === 'Xv';
176
- return (h("header", { key: '9477fea03faf7b1eee3a59be46a6b6efffabe7ae', class: "header" }, h("div", { key: '974c597e788966d68bbc0af46ef1c56e5d49c291', class: "header-wrapper" }, h("div", { key: 'c4efe27850b74ccaf75864abbee66e04fdd39aba', class: "xv-container" }, h("div", { key: '0dc3df2ffc4dbae8caeb996cbd6bd1293c51126f', class: "header-top" }, h("div", { key: 'fc2ba21ef07a79bf88aa94049222f7ad31842509', class: "header-top--left" }, h("xv-button-v2", { key: '13fc5ad1a0b7587ee223a55c5a6f5b5490935c76', class: "menu-btn", variant: "ghost", onClick: this.toggleMobMenu }, h("i", { key: 'f8baece2d445514f1e30083d109e9a547d84aa9a', slot: "icon-left", class: { 'fa': true, 'fa-times': this.mobMenuOpen, 'fa-bars': !this.mobMenuOpen } })), this.metaNav && (h("a", { key: '506f618928c24e1a8d804e1b362fdc8fdd6662e7', href: `${window.location.origin}/` }, h("picture", { key: '4c0ddbce5bac9bdb282bbff733e9bb781dd47645' }, h("source", { key: '3083b3d605b1c2d30f5dc1935cf1e298b93c5c19', srcSet: ((_b = this.metaNav) === null || _b === void 0 ? void 0 : _b.LogoUrl) || 'https://www.crossvertise.com/ResourcePackages/Bootstrap/assets/dist/images/logo.svg', media: "(min-width: 990px)" }), h("img", { key: '96f2386509b36fd81954819936154751f042fce0', src: ((_c = this.metaNav) === null || _c === void 0 ? void 0 : _c.LogoSmallUrl) || 'https://www.crossvertise.com/ResourcePackages/Bootstrap/assets/dist/images/logo-small.svg', onError: this.logoFallback, loading: "lazy", alt: "Crossvertise" })))), this.orgUnit && (h("xv-link-v2", { key: '3bb46d9af623db9e54c2c51630a83ce4e384b479', class: "header-top--orgUnit", variant: "standalone", href: "/de-de/mycrossvertise" }, this.orgUnit))), h("ul", { key: '36e14a1481748def91b5e76f82631e76d35ae49c', class: "header-top--right" }, this._renderSearch(), this._renderImpersonationInfo(), !((_d = this.metaNav) === null || _d === void 0 ? void 0 : _d.IsManager) && isXvBrand && (h("li", { key: '07c00dae34dfd6f177ff3803f88755339422eec4' }, h("xv-link-v2", { key: '573110e4de76b5a9aab3bb5c21519b68a4236de7', variant: "ghost", href: "https://www.crossvertise.com/ueber-uns/kontakt" }, h("xv-button-v2", { key: '318e47e08a44e34bf10a38d8c2901a2d64bfcf2b', variant: "ghost", size: SIZE_VAR.MD }, h("i", { key: 'd44b57c9ccd07db75c19dae59123c0320241cb3e', class: "header-icon far fa-envelope" }), h("span", { key: 'c9e66d1395ab5b690a2e673994a137807232a084', class: "header-label xv-body-02" }, "Kontakt"))))), this._renderNotifications(), this._renderAccount(), !((_e = this.metaNav) === null || _e === void 0 ? void 0 : _e.IsManager) && isXvBrand && (h("li", { key: 'ca2f1556456c788b78c4929f2d64e7c7f239c4fe' }, h("xv-link-v2", { key: '47f0a74790fb2331a404d515e5fbe8369458146c', variant: "ghost", href: "https://help.crossvertise.com" }, h("xv-button-v2", { key: 'ae850b8f6d7c1da9ea3ba704adb1d670720dff7c', variant: "ghost", size: SIZE_VAR.MD }, h("i", { key: '8c8a857e172f937d8eba8083a63e2ff8662e06cb', class: "header-icon far fa-question-circle" }), h("span", { key: 'bb86a87d43293d313654440a126c4e8ea5d228c9', class: "header-label xv-body-02" }, "Hilfe"))))), this._renderShoppingCart()))), h("div", { key: '4b0386ef89ed643cd42b6232b10064d6bc73beab', class: "header-wrapper--bg" }, h("div", { key: '83ef95df9b7820eae211b958f6f1bf57d046905b', class: "xv-container" }, h("xv-mega-menu", { key: '763117fc0da850b8b1bbea389dcaa3cfacef4847', open: this.mobMenuOpen, onOpenChange: this.closeMobMenu })))), h("xv-login-modal", { key: 'b7252b1eb7527f6dbab4a1cca972d47b75a66b14', visible: this.showLoginModal, onClose: () => this.showLoginModal = false })));
257
+ return (h("header", { key: 'e9157c2348f2e286e05e7b694d692ed88c80a543', class: "header" }, h("div", { key: 'ce7a4aa5e9820598eff8331b8f9ae6f57cc2c034', class: "header-wrapper" }, h("div", { key: '7bbec18df899b1234e8409b0c3723719180057ba', class: "xv-container" }, h("div", { key: 'f733864d082f8d23f69cb57961bdffeea60c7657', class: "header-top" }, h("div", { key: '63df8d29d16e79ef15cf5c5733a2f9b7662a990e', class: "header-top--left" }, h("xv-button-v2", { key: '5a782cdff61ce2fb4b9a5685d93d5c36a6a0fc9e', class: "menu-btn", variant: "ghost", onClick: this.toggleMobMenu }, h("i", { key: '2388b08c95773c3b08eeadf5acbe101119cc996d', slot: "icon-left", class: { 'fa': true, 'fa-times': this.mobMenuOpen, 'fa-bars': !this.mobMenuOpen } })), this.metaNav && (h("a", { key: '277a9e5179a10208e09b0896e234fd6abac4e6d4', href: `${window.location.origin}/` }, h("picture", { key: 'f07378b064d3af36a6f3059fd1665544eecd17be' }, h("source", { key: '719e3c4b4147df467f36c45ba91457c3b7724a35', srcSet: ((_b = this.metaNav) === null || _b === void 0 ? void 0 : _b.LogoUrl) || 'https://www.crossvertise.com/ResourcePackages/Bootstrap/assets/dist/images/logo.svg', media: "(min-width: 990px)" }), h("img", { key: 'cd5a8c6bcd09d0254b3518410efe1b45b9922b97', src: ((_c = this.metaNav) === null || _c === void 0 ? void 0 : _c.LogoSmallUrl) || 'https://www.crossvertise.com/ResourcePackages/Bootstrap/assets/dist/images/logo-small.svg', onError: this.logoFallback, loading: "lazy", alt: "Crossvertise" })))), this.orgUnit && (h("xv-link-v2", { key: '4775620b83865d75a8fdb1201ceca5bbe543ad67', class: "header-top--orgUnit", variant: "standalone", href: "/de-de/mycrossvertise" }, this.orgUnit))), h("ul", { key: 'c3b86b2b97c3fd04fe471c5d2ae49d2b7fc03d27', class: "header-top--right" }, this._renderSearch(), this._renderImpersonationInfo(), !((_d = this.metaNav) === null || _d === void 0 ? void 0 : _d.IsManager) && isXvBrand && (h("li", { key: '9bfbc4f0ba394fc1b36dbb62f86e334cb06257b3' }, h("xv-link-v2", { key: 'bca968a2fe64bf6872d43d8ac7f10d78b089a23a', variant: "ghost", href: "https://www.crossvertise.com/ueber-uns/kontakt" }, h("xv-button-v2", { key: '0db4cf67178bb86beefe8dddf7b378171e15198d', variant: "ghost", size: SIZE_VAR.MD }, h("i", { key: 'ed1b26d33fc9b843a0c89b05a4bb609273fa8ce8', class: "header-icon far fa-envelope" }), h("span", { key: '4e8f6fea781c552338e248b111a24ad289526d46', class: "header-label xv-body-02" }, "Kontakt"))))), this._renderNotifications(), this._renderAccount(), !((_e = this.metaNav) === null || _e === void 0 ? void 0 : _e.IsManager) && isXvBrand && (h("li", { key: '23f641be3c9218dac34b8abe6b2cbb263c856843' }, h("xv-link-v2", { key: 'b4315fa914b8173f92092aeb3735fabbd5986d25', variant: "ghost", href: "https://help.crossvertise.com" }, h("xv-button-v2", { key: '0bec228884977d7c88549a24fe4e62f76a11753b', variant: "ghost", size: SIZE_VAR.MD }, h("i", { key: 'd23ccaed77af7631742f23c053ff718278012d4e', class: "header-icon far fa-question-circle" }), h("span", { key: '0771508d8bdffa99d1e4748141e085191c421bf7', class: "header-label xv-body-02" }, "Hilfe"))))), this._renderShoppingCart()))), h("div", { key: '97bf836216e1ac3f1d8b0b4ed96b7e0875f6cdcc', class: "header-wrapper--bg" }, h("div", { key: '9d542f3553673d6c5c27a74b81d899b56562e724', class: "xv-container" }, h("xv-mega-menu", { key: 'b8b0af38f25546dd3d48a76a4fd7e381982e482e', open: this.mobMenuOpen, onOpenChange: this.closeMobMenu })))), h("xv-login-modal", { key: 'cad3944d560a74e29399f54c5e47b993a7e7dbe4', visible: this.showLoginModal, onClose: () => this.showLoginModal = false })));
177
258
  }
178
259
  static get is() { return "xv-header"; }
179
260
  static get originalStyleUrls() {