@xiwebdesign/xicms-core 7.31.0 → 8.16.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.
- package/dist/XiCMSProvider.js +23 -7
- package/dist/XiCMSProvider.js.map +1 -1
- package/dist/admin/AdminApp.js +175 -93
- package/dist/admin/AdminApp.js.map +1 -1
- package/dist/admin/AdminHelp.d.ts +1 -1
- package/dist/admin/AdminHelp.js +44 -36
- package/dist/admin/AdminHelp.js.map +1 -1
- package/dist/admin/CoreVersion.d.ts +6 -3
- package/dist/admin/CoreVersion.js +60 -24
- package/dist/admin/CoreVersion.js.map +1 -1
- package/dist/admin/NewsEditor.d.ts +7 -0
- package/dist/admin/NewsEditor.js +117 -75
- package/dist/admin/NewsEditor.js.map +1 -1
- package/dist/admin/SuperadminPanel.js +1 -1
- package/dist/admin/SuperadminPanel.js.map +1 -1
- package/dist/admin/ZoekPalet.d.ts +15 -0
- package/dist/admin/ZoekPalet.js +123 -0
- package/dist/admin/ZoekPalet.js.map +1 -0
- package/dist/admin/blockEditors/GenericEditor.js +36 -18
- package/dist/admin/blockEditors/GenericEditor.js.map +1 -1
- package/dist/admin/blockEditors/StyleEditor.js +37 -3
- package/dist/admin/blockEditors/StyleEditor.js.map +1 -1
- package/dist/admin/blockEditors/SymbolRefEditor.js +44 -2
- package/dist/admin/blockEditors/SymbolRefEditor.js.map +1 -1
- package/dist/admin/blockEditors/index.js +154 -73
- package/dist/admin/blockEditors/index.js.map +1 -1
- package/dist/admin/customBlocks/CustomBlocksPanel.js +18 -7
- package/dist/admin/customBlocks/CustomBlocksPanel.js.map +1 -1
- package/dist/admin/dashboard/DashboardPanel.js +26 -16
- package/dist/admin/dashboard/DashboardPanel.js.map +1 -1
- package/dist/admin/editors/RichTextEditor.d.ts +4 -3
- package/dist/admin/editors/RichTextEditor.js +19 -60
- package/dist/admin/editors/RichTextEditor.js.map +1 -1
- package/dist/admin/editors/fontSizeExtension.js +7 -1
- package/dist/admin/editors/fontSizeExtension.js.map +1 -1
- package/dist/admin/editors/slashMenu.d.ts +18 -0
- package/dist/admin/editors/slashMenu.js +34 -0
- package/dist/admin/editors/slashMenu.js.map +1 -0
- package/dist/admin/editors/useRijkeTekst.d.ts +48 -0
- package/dist/admin/editors/useRijkeTekst.js +310 -0
- package/dist/admin/editors/useRijkeTekst.js.map +1 -0
- package/dist/admin/forms/FormsPanel.d.ts +11 -2
- package/dist/admin/forms/FormsPanel.js +218 -65
- package/dist/admin/forms/FormsPanel.js.map +1 -1
- package/dist/admin/lib/blokInfo.d.ts +17 -0
- package/dist/admin/lib/blokInfo.js +80 -0
- package/dist/admin/lib/blokInfo.js.map +1 -0
- package/dist/admin/lib/foutNaarMens.d.ts +1 -0
- package/dist/admin/lib/foutNaarMens.js +22 -0
- package/dist/admin/lib/foutNaarMens.js.map +1 -0
- package/dist/admin/lib/gewisteVelden.d.ts +13 -0
- package/dist/admin/lib/gewisteVelden.js +31 -0
- package/dist/admin/lib/gewisteVelden.js.map +1 -0
- package/dist/admin/lib/heroHulp.d.ts +11 -0
- package/dist/admin/lib/heroHulp.js +18 -0
- package/dist/admin/lib/heroHulp.js.map +1 -0
- package/dist/admin/lib/kleur.js +2 -3
- package/dist/admin/lib/kleur.js.map +1 -1
- package/dist/admin/lib/leesbaarheid.d.ts +34 -0
- package/dist/admin/lib/leesbaarheid.js +82 -0
- package/dist/admin/lib/leesbaarheid.js.map +1 -0
- package/dist/admin/lib/lengteAdvies.d.ts +1 -0
- package/dist/admin/lib/lengteAdvies.js +16 -0
- package/dist/admin/lib/lengteAdvies.js.map +1 -0
- package/dist/admin/lib/mediaLijst.d.ts +26 -0
- package/dist/admin/lib/mediaLijst.js +42 -0
- package/dist/admin/lib/mediaLijst.js.map +1 -0
- package/dist/admin/lib/mediaUsage.d.ts +12 -1
- package/dist/admin/lib/mediaUsage.js +43 -33
- package/dist/admin/lib/mediaUsage.js.map +1 -1
- package/dist/admin/lib/menuHulp.d.ts +16 -0
- package/dist/admin/lib/menuHulp.js +41 -0
- package/dist/admin/lib/menuHulp.js.map +1 -0
- package/dist/admin/lib/onopgeslagen.d.ts +10 -0
- package/dist/admin/lib/onopgeslagen.js +35 -0
- package/dist/admin/lib/onopgeslagen.js.map +1 -0
- package/dist/admin/lib/pagesClient.d.ts +55 -2
- package/dist/admin/lib/pagesClient.js +126 -28
- package/dist/admin/lib/pagesClient.js.map +1 -1
- package/dist/admin/lib/paginaGebruik.d.ts +56 -0
- package/dist/admin/lib/paginaGebruik.js +109 -0
- package/dist/admin/lib/paginaGebruik.js.map +1 -0
- package/dist/admin/lib/publicatieCheck.d.ts +25 -0
- package/dist/admin/lib/publicatieCheck.js +114 -0
- package/dist/admin/lib/publicatieCheck.js.map +1 -0
- package/dist/admin/lib/scrollNaarDetail.d.ts +6 -0
- package/dist/admin/lib/scrollNaarDetail.js +20 -0
- package/dist/admin/lib/scrollNaarDetail.js.map +1 -0
- package/dist/admin/lib/symbolsClient.d.ts +10 -0
- package/dist/admin/lib/symbolsClient.js +14 -1
- package/dist/admin/lib/symbolsClient.js.map +1 -1
- package/dist/admin/lib/tekstVeld.d.ts +8 -0
- package/dist/admin/lib/tekstVeld.js +10 -0
- package/dist/admin/lib/tekstVeld.js.map +1 -0
- package/dist/admin/lib/useIsSuperadmin.d.ts +2 -0
- package/dist/admin/lib/useIsSuperadmin.js +13 -0
- package/dist/admin/lib/useIsSuperadmin.js.map +1 -0
- package/dist/admin/lib/veldTaal.d.ts +14 -0
- package/dist/admin/lib/veldTaal.js +248 -0
- package/dist/admin/lib/veldTaal.js.map +1 -0
- package/dist/admin/lib/versieVergelijk.d.ts +17 -0
- package/dist/admin/lib/versieVergelijk.js +24 -0
- package/dist/admin/lib/versieVergelijk.js.map +1 -0
- package/dist/admin/lib/zodFields.js +22 -4
- package/dist/admin/lib/zodFields.js.map +1 -1
- package/dist/admin/lib/zodFout.d.ts +9 -0
- package/dist/admin/lib/zodFout.js +63 -0
- package/dist/admin/lib/zodFout.js.map +1 -0
- package/dist/admin/media/MediaManager.js +162 -32
- package/dist/admin/media/MediaManager.js.map +1 -1
- package/dist/admin/media/MediaPicker.d.ts +3 -0
- package/dist/admin/media/MediaPicker.js +84 -31
- package/dist/admin/media/MediaPicker.js.map +1 -1
- package/dist/admin/menu/MenuPanel.js +144 -83
- package/dist/admin/menu/MenuPanel.js.map +1 -1
- package/dist/admin/navContext.d.ts +3 -0
- package/dist/admin/navContext.js +13 -1
- package/dist/admin/navContext.js.map +1 -1
- package/dist/admin/pages/PageEditor.js +146 -30
- package/dist/admin/pages/PageEditor.js.map +1 -1
- package/dist/admin/pages/PagesPanel.js +215 -61
- package/dist/admin/pages/PagesPanel.js.map +1 -1
- package/dist/admin/pages/StarterKiezer.d.ts +12 -0
- package/dist/admin/pages/StarterKiezer.js +113 -0
- package/dist/admin/pages/StarterKiezer.js.map +1 -0
- package/dist/admin/panelRegistry.d.ts +1 -1
- package/dist/admin/panelRegistry.js +2 -1
- package/dist/admin/panelRegistry.js.map +1 -1
- package/dist/admin/patterns/PatternInserter.js +1 -1
- package/dist/admin/patterns/PatternInserter.js.map +1 -1
- package/dist/admin/settings/BedrijfInstellingen.d.ts +20 -0
- package/dist/admin/settings/BedrijfInstellingen.js +159 -0
- package/dist/admin/settings/BedrijfInstellingen.js.map +1 -0
- package/dist/admin/settings/MailTemplates.js +5 -4
- package/dist/admin/settings/MailTemplates.js.map +1 -1
- package/dist/admin/settings/SettingsPanel.js +120 -78
- package/dist/admin/settings/SettingsPanel.js.map +1 -1
- package/dist/admin/settings/SnapshotsCard.js +4 -4
- package/dist/admin/settings/SnapshotsCard.js.map +1 -1
- package/dist/admin/settings/VorigeTerugzetten.d.ts +15 -0
- package/dist/admin/settings/VorigeTerugzetten.js +89 -0
- package/dist/admin/settings/VorigeTerugzetten.js.map +1 -0
- package/dist/admin/shop/CategoriesTab.js +6 -4
- package/dist/admin/shop/CategoriesTab.js.map +1 -1
- package/dist/admin/shop/DiscountsTab.js +4 -2
- package/dist/admin/shop/DiscountsTab.js.map +1 -1
- package/dist/admin/shop/OrdersTab.js +57 -19
- package/dist/admin/shop/OrdersTab.js.map +1 -1
- package/dist/admin/shop/ProductPaneel.js +2 -1
- package/dist/admin/shop/ProductPaneel.js.map +1 -1
- package/dist/admin/shop/ProductsTab.js +40 -19
- package/dist/admin/shop/ProductsTab.js.map +1 -1
- package/dist/admin/shop/ShopPanel.js +13 -1
- package/dist/admin/shop/ShopPanel.js.map +1 -1
- package/dist/admin/shop/ShopSettingsTab.js +56 -17
- package/dist/admin/shop/ShopSettingsTab.js.map +1 -1
- package/dist/admin/shop/productClient.js +7 -3
- package/dist/admin/shop/productClient.js.map +1 -1
- package/dist/admin/stats/NietGevonden.d.ts +10 -0
- package/dist/admin/stats/NietGevonden.js +78 -0
- package/dist/admin/stats/NietGevonden.js.map +1 -0
- package/dist/admin/stats/StatsPanel.js +6 -1
- package/dist/admin/stats/StatsPanel.js.map +1 -1
- package/dist/admin/stats/lib.d.ts +2 -0
- package/dist/admin/stats/lib.js.map +1 -1
- package/dist/admin/ui/ConfirmDialog.js +10 -1
- package/dist/admin/ui/ConfirmDialog.js.map +1 -1
- package/dist/admin/ui/Icon.d.ts +34 -0
- package/dist/admin/ui/Icon.js +44 -0
- package/dist/admin/ui/Icon.js.map +1 -0
- package/dist/admin/ui/ImageField.d.ts +15 -0
- package/dist/admin/ui/ImageField.js +67 -0
- package/dist/admin/ui/ImageField.js.map +1 -0
- package/dist/admin/ui/KleurVeld.d.ts +25 -0
- package/dist/admin/ui/KleurVeld.js +76 -0
- package/dist/admin/ui/KleurVeld.js.map +1 -0
- package/dist/admin/ui/LaadStaat.d.ts +21 -0
- package/dist/admin/ui/LaadStaat.js +43 -0
- package/dist/admin/ui/LaadStaat.js.map +1 -0
- package/dist/admin/ui/LeesbaarheidPaneel.d.ts +10 -0
- package/dist/admin/ui/LeesbaarheidPaneel.js +68 -0
- package/dist/admin/ui/LeesbaarheidPaneel.js.map +1 -0
- package/dist/admin/ui/LinkDialog.d.ts +22 -0
- package/dist/admin/ui/LinkDialog.js +131 -0
- package/dist/admin/ui/LinkDialog.js.map +1 -0
- package/dist/admin/ui/LinkVeld.d.ts +14 -0
- package/dist/admin/ui/LinkVeld.js +49 -0
- package/dist/admin/ui/LinkVeld.js.map +1 -0
- package/dist/admin/ui/PreviewLinkResultaat.d.ts +14 -0
- package/dist/admin/ui/PreviewLinkResultaat.js +52 -0
- package/dist/admin/ui/PreviewLinkResultaat.js.map +1 -0
- package/dist/admin/ui/charts.js +5 -5
- package/dist/admin/ui/charts.js.map +1 -1
- package/dist/admin/ui/primitives.d.ts +16 -6
- package/dist/admin/ui/primitives.js +98 -26
- package/dist/admin/ui/primitives.js.map +1 -1
- package/dist/admin/ui/select.js +5 -2
- package/dist/admin/ui/select.js.map +1 -1
- package/dist/admin/ui/switch.d.ts +3 -1
- package/dist/admin/ui/switch.js +6 -3
- package/dist/admin/ui/switch.js.map +1 -1
- package/dist/admin/ui/toast.d.ts +2 -1
- package/dist/admin/ui/toast.js +17 -11
- package/dist/admin/ui/toast.js.map +1 -1
- package/dist/admin/visual/BlokKiezer.d.ts +34 -0
- package/dist/admin/visual/BlokKiezer.js +225 -0
- package/dist/admin/visual/BlokKiezer.js.map +1 -0
- package/dist/admin/visual/InlineGereedschap.d.ts +52 -0
- package/dist/admin/visual/InlineGereedschap.js +356 -0
- package/dist/admin/visual/InlineGereedschap.js.map +1 -0
- package/dist/admin/visual/InlineRijkeTekst.d.ts +23 -0
- package/dist/admin/visual/InlineRijkeTekst.js +152 -0
- package/dist/admin/visual/InlineRijkeTekst.js.map +1 -0
- package/dist/admin/visual/VisualEditor.js +273 -125
- package/dist/admin/visual/VisualEditor.js.map +1 -1
- package/dist/admin.css +1 -1
- package/dist/auth/AuthProvider.js +1 -0
- package/dist/auth/AuthProvider.js.map +1 -1
- package/dist/bedrijf/bedrijf.d.ts +44 -0
- package/dist/bedrijf/bedrijf.js +125 -0
- package/dist/bedrijf/bedrijf.js.map +1 -0
- package/dist/blocks/BlockRenderer.js +11 -1
- package/dist/blocks/BlockRenderer.js.map +1 -1
- package/dist/blocks/blockStyle.d.ts +7 -0
- package/dist/blocks/blockStyle.js +16 -3
- package/dist/blocks/blockStyle.js.map +1 -1
- package/dist/blocks/builtinSchemas.js +6 -0
- package/dist/blocks/builtinSchemas.js.map +1 -1
- package/dist/blocks/builtins/AgendaRender.d.ts +22 -0
- package/dist/blocks/builtins/AgendaRender.js +69 -9
- package/dist/blocks/builtins/AgendaRender.js.map +1 -1
- package/dist/blocks/builtins/Availability.d.ts +1 -0
- package/dist/blocks/builtins/Availability.js +23 -0
- package/dist/blocks/builtins/Availability.js.map +1 -0
- package/dist/blocks/builtins/AvailabilityRender.d.ts +12 -0
- package/dist/blocks/builtins/AvailabilityRender.js +105 -0
- package/dist/blocks/builtins/AvailabilityRender.js.map +1 -0
- package/dist/blocks/builtins/BeforeAfterRender.js +4 -3
- package/dist/blocks/builtins/BeforeAfterRender.js.map +1 -1
- package/dist/blocks/builtins/Booking.js +1 -1
- package/dist/blocks/builtins/Booking.js.map +1 -1
- package/dist/blocks/builtins/BreadcrumbsRender.d.ts +4 -3
- package/dist/blocks/builtins/BreadcrumbsRender.js +14 -8
- package/dist/blocks/builtins/BreadcrumbsRender.js.map +1 -1
- package/dist/blocks/builtins/ColumnsRender.d.ts +1 -1
- package/dist/blocks/builtins/ColumnsRender.js +3 -1
- package/dist/blocks/builtins/ColumnsRender.js.map +1 -1
- package/dist/blocks/builtins/Contact.js +1 -1
- package/dist/blocks/builtins/Contact.js.map +1 -1
- package/dist/blocks/builtins/ContactRender.d.ts +1 -1
- package/dist/blocks/builtins/ContactRender.js +37 -8
- package/dist/blocks/builtins/ContactRender.js.map +1 -1
- package/dist/blocks/builtins/Cta.js +1 -1
- package/dist/blocks/builtins/Cta.js.map +1 -1
- package/dist/blocks/builtins/CtaRender.d.ts +1 -1
- package/dist/blocks/builtins/CtaRender.js +5 -4
- package/dist/blocks/builtins/CtaRender.js.map +1 -1
- package/dist/blocks/builtins/Embed.js +1 -1
- package/dist/blocks/builtins/Embed.js.map +1 -1
- package/dist/blocks/builtins/FormBlock.js +1 -0
- package/dist/blocks/builtins/FormBlock.js.map +1 -1
- package/dist/blocks/builtins/FormBlockRender.d.ts +6 -0
- package/dist/blocks/builtins/FormBlockRender.js +88 -16
- package/dist/blocks/builtins/FormBlockRender.js.map +1 -1
- package/dist/blocks/builtins/GalleryRender.js +3 -2
- package/dist/blocks/builtins/GalleryRender.js.map +1 -1
- package/dist/blocks/builtins/Hero.js +1 -1
- package/dist/blocks/builtins/Hero.js.map +1 -1
- package/dist/blocks/builtins/HeroRender.js +7 -3
- package/dist/blocks/builtins/HeroRender.js.map +1 -1
- package/dist/blocks/builtins/OpenNowBadge.d.ts +4 -2
- package/dist/blocks/builtins/OpenNowBadge.js +6 -6
- package/dist/blocks/builtins/OpenNowBadge.js.map +1 -1
- package/dist/blocks/builtins/OpeningHoursRender.d.ts +8 -2
- package/dist/blocks/builtins/OpeningHoursRender.js +27 -6
- package/dist/blocks/builtins/OpeningHoursRender.js.map +1 -1
- package/dist/blocks/builtins/PriceList.d.ts +1 -0
- package/dist/blocks/builtins/PriceList.js +25 -0
- package/dist/blocks/builtins/PriceList.js.map +1 -0
- package/dist/blocks/builtins/PriceListRender.d.ts +17 -0
- package/dist/blocks/builtins/PriceListRender.js +87 -0
- package/dist/blocks/builtins/PriceListRender.js.map +1 -0
- package/dist/blocks/builtins/RichTextBlockRender.js +6 -1
- package/dist/blocks/builtins/RichTextBlockRender.js.map +1 -1
- package/dist/blocks/builtins/SectionHeading.d.ts +1 -0
- package/dist/blocks/builtins/SectionHeading.js +14 -0
- package/dist/blocks/builtins/SectionHeading.js.map +1 -0
- package/dist/blocks/builtins/SectionHeadingRender.d.ts +7 -0
- package/dist/blocks/builtins/SectionHeadingRender.js +14 -0
- package/dist/blocks/builtins/SectionHeadingRender.js.map +1 -0
- package/dist/blocks/builtins/SliderRender.js +3 -2
- package/dist/blocks/builtins/SliderRender.js.map +1 -1
- package/dist/blocks/builtins/SymbolRef.js +1 -1
- package/dist/blocks/builtins/SymbolRef.js.map +1 -1
- package/dist/blocks/builtins/TeamRender.js +6 -1
- package/dist/blocks/builtins/TeamRender.js.map +1 -1
- package/dist/blocks/builtins/Testimonials.js +3 -1
- package/dist/blocks/builtins/Testimonials.js.map +1 -1
- package/dist/blocks/builtins/TestimonialsRender.js +4 -0
- package/dist/blocks/builtins/TestimonialsRender.js.map +1 -1
- package/dist/blocks/builtins/WhatsappChatRender.d.ts +1 -1
- package/dist/blocks/builtins/WhatsappChatRender.js +3 -1
- package/dist/blocks/builtins/WhatsappChatRender.js.map +1 -1
- package/dist/blocks/builtins/Widgets.js +2 -2
- package/dist/blocks/builtins/Widgets.js.map +1 -1
- package/dist/blocks/builtins/WidgetsRender.js +3 -1
- package/dist/blocks/builtins/WidgetsRender.js.map +1 -1
- package/dist/blocks/builtins/consentEmbed.js +26 -9
- package/dist/blocks/builtins/consentEmbed.js.map +1 -1
- package/dist/blocks/builtins/index.d.ts +3 -0
- package/dist/blocks/builtins/index.js +6 -0
- package/dist/blocks/builtins/index.js.map +1 -1
- package/dist/blocks/builtins/lib/autoComplete.d.ts +7 -0
- package/dist/blocks/builtins/lib/autoComplete.js +20 -0
- package/dist/blocks/builtins/lib/autoComplete.js.map +1 -0
- package/dist/blocks/builtins/lib/bookingUrl.d.ts +2 -2
- package/dist/blocks/builtins/lib/bookingUrl.js +1 -1
- package/dist/blocks/builtins/lib/bookingUrl.js.map +1 -1
- package/dist/blocks/builtins/lib/ical.d.ts +24 -0
- package/dist/blocks/builtins/lib/ical.js +82 -0
- package/dist/blocks/builtins/lib/ical.js.map +1 -0
- package/dist/blocks/builtins/lib/vandaag.d.ts +8 -0
- package/dist/blocks/builtins/lib/vandaag.js +22 -0
- package/dist/blocks/builtins/lib/vandaag.js.map +1 -0
- package/dist/blocks/registry.d.ts +6 -0
- package/dist/blocks/registry.js.map +1 -1
- package/dist/blocks/ui/index.js +1 -0
- package/dist/blocks/ui/index.js.map +1 -1
- package/dist/components/EditBar.js +2 -4
- package/dist/components/EditBar.js.map +1 -1
- package/dist/components/News.js +26 -18
- package/dist/components/News.js.map +1 -1
- package/dist/components/PageView.d.ts +7 -1
- package/dist/components/PageView.js +17 -0
- package/dist/components/PageView.js.map +1 -1
- package/dist/components/ResponsiveImage.js +18 -20
- package/dist/components/ResponsiveImage.js.map +1 -1
- package/dist/components/onderOffset.d.ts +13 -0
- package/dist/components/onderOffset.js +74 -0
- package/dist/components/onderOffset.js.map +1 -0
- package/dist/components/site/Bedrijfsnummers.d.ts +9 -0
- package/dist/components/site/Bedrijfsnummers.js +12 -0
- package/dist/components/site/Bedrijfsnummers.js.map +1 -0
- package/dist/components/site/SiteFooter.d.ts +6 -1
- package/dist/components/site/SiteFooter.js +62 -9
- package/dist/components/site/SiteFooter.js.map +1 -1
- package/dist/components/site/SiteHeader.d.ts +15 -0
- package/dist/components/site/SiteHeader.js +70 -0
- package/dist/components/site/SiteHeader.js.map +1 -0
- package/dist/components/site/SiteNav.d.ts +2 -0
- package/dist/components/site/SiteNav.js +29 -5
- package/dist/components/site/SiteNav.js.map +1 -1
- package/dist/demo/DemoStudio.js +2 -2
- package/dist/demo/DemoStudio.js.map +1 -1
- package/dist/demo/demoStore.d.ts +13 -0
- package/dist/demo/demoStore.js +36 -0
- package/dist/demo/demoStore.js.map +1 -1
- package/dist/editor/EditorProvider.js +15 -4
- package/dist/editor/EditorProvider.js.map +1 -1
- package/dist/firebase/functionsClient.js +5 -1
- package/dist/firebase/functionsClient.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +20 -1
- package/dist/index.js.map +1 -1
- package/dist/media/ImageContext.d.ts +17 -0
- package/dist/media/ImageContext.js +65 -20
- package/dist/media/ImageContext.js.map +1 -1
- package/dist/media/altInHtml.d.ts +9 -0
- package/dist/media/altInHtml.js +17 -0
- package/dist/media/altInHtml.js.map +1 -0
- package/dist/media/srcset.d.ts +12 -0
- package/dist/media/srcset.js +13 -0
- package/dist/media/srcset.js.map +1 -0
- package/dist/modules/builtins.d.ts +1 -0
- package/dist/modules/builtins.js +22 -9
- package/dist/modules/builtins.js.map +1 -1
- package/dist/modules/consent/ConsentBanner.d.ts +5 -0
- package/dist/modules/consent/ConsentBanner.js +8 -7
- package/dist/modules/consent/ConsentBanner.js.map +1 -1
- package/dist/modules/consent/standaard.d.ts +12 -0
- package/dist/modules/consent/standaard.js +10 -0
- package/dist/modules/consent/standaard.js.map +1 -0
- package/dist/modules/consent/useConsent.d.ts +4 -0
- package/dist/modules/consent/useConsent.js +30 -1
- package/dist/modules/consent/useConsent.js.map +1 -1
- package/dist/modules/shop/blocks/Cart.js +5 -3
- package/dist/modules/shop/blocks/Cart.js.map +1 -1
- package/dist/modules/shop/blocks/Checkout.js +56 -35
- package/dist/modules/shop/blocks/Checkout.js.map +1 -1
- package/dist/modules/shop/blocks/Donation.js +4 -4
- package/dist/modules/shop/blocks/Donation.js.map +1 -1
- package/dist/modules/shop/blocks/ProductDetail.js +19 -3
- package/dist/modules/shop/blocks/ProductDetail.js.map +1 -1
- package/dist/modules/shop/blocks/ProductGrid.d.ts +3 -1
- package/dist/modules/shop/blocks/ProductGrid.js +8 -4
- package/dist/modules/shop/blocks/ProductGrid.js.map +1 -1
- package/dist/modules/shop/blocks/ShopCategories.js +4 -3
- package/dist/modules/shop/blocks/ShopCategories.js.map +1 -1
- package/dist/modules/shop/lib.d.ts +4 -0
- package/dist/modules/shop/lib.js +14 -0
- package/dist/modules/shop/lib.js.map +1 -1
- package/dist/modules/shop/settings.d.ts +3 -0
- package/dist/modules/shop/settings.js +14 -0
- package/dist/modules/shop/settings.js.map +1 -1
- package/dist/modules/shop/strings.d.ts +4 -0
- package/dist/modules/shop/strings.js +8 -0
- package/dist/modules/shop/strings.js.map +1 -1
- package/dist/next/envCheck.d.ts +42 -0
- package/dist/next/envCheck.js +106 -0
- package/dist/next/envCheck.js.map +1 -0
- package/dist/next/index.cjs +185 -2
- package/dist/next/index.d.ts +2 -0
- package/dist/next/index.js +8 -1
- package/dist/next/index.js.map +1 -1
- package/dist/next/securityHeaders.d.ts +36 -0
- package/dist/next/securityHeaders.js +74 -0
- package/dist/next/securityHeaders.js.map +1 -0
- package/dist/patterns/builtins.d.ts +7 -0
- package/dist/patterns/builtins.js +138 -3
- package/dist/patterns/builtins.js.map +1 -1
- package/dist/releaseNotes.js +148 -0
- package/dist/releaseNotes.js.map +1 -1
- package/dist/render/PublishedPage.d.ts +2 -1
- package/dist/render/PublishedPage.js +10 -4
- package/dist/render/PublishedPage.js.map +1 -1
- package/dist/schema/blocks.d.ts +473 -0
- package/dist/schema/blocks.js +105 -5
- package/dist/schema/blocks.js.map +1 -1
- package/dist/schema/business.d.ts +118 -0
- package/dist/schema/business.js +75 -0
- package/dist/schema/business.js.map +1 -0
- package/dist/schema/fields.d.ts +2 -1
- package/dist/schema/fields.js.map +1 -1
- package/dist/schema/forms.d.ts +18 -0
- package/dist/schema/forms.js +18 -3
- package/dist/schema/forms.js.map +1 -1
- package/dist/schema/mail.d.ts +10 -0
- package/dist/schema/mail.js +7 -0
- package/dist/schema/mail.js.map +1 -1
- package/dist/schema/settings.d.ts +103 -0
- package/dist/schema/settings.js +24 -3
- package/dist/schema/settings.js.map +1 -1
- package/dist/schema/shop.d.ts +2 -0
- package/dist/schema/shop.js +4 -0
- package/dist/schema/shop.js.map +1 -1
- package/dist/schema/shopGeld.d.ts +1 -0
- package/dist/schema/shopGeld.js +1 -0
- package/dist/schema/shopGeld.js.map +1 -1
- package/dist/schema/siteConfig.d.ts +69 -1
- package/dist/schema/siteConfig.js +7 -2
- package/dist/schema/siteConfig.js.map +1 -1
- package/dist/server/blockData.d.ts +1 -1
- package/dist/server/blockData.js +74 -18
- package/dist/server/blockData.js.map +1 -1
- package/dist/server/cspRapport.d.ts +20 -0
- package/dist/server/cspRapport.js +56 -0
- package/dist/server/cspRapport.js.map +1 -0
- package/dist/server/fonts.d.ts +3 -0
- package/dist/server/fonts.js +39 -0
- package/dist/server/fonts.js.map +1 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +8 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/sanitize.js +2 -0
- package/dist/server/sanitize.js.map +1 -1
- package/dist/settings/useSiteSettings.js +4 -1
- package/dist/settings/useSiteSettings.js.map +1 -1
- package/dist/starters/starters.d.ts +69 -0
- package/dist/starters/starters.js +171 -0
- package/dist/starters/starters.js.map +1 -0
- package/dist/styles.css +1181 -212
- package/dist/theme/color.d.ts +10 -0
- package/dist/theme/color.js +17 -1
- package/dist/theme/color.js.map +1 -1
- package/dist/theme/fontFallback.d.ts +23 -0
- package/dist/theme/fontFallback.js +58 -0
- package/dist/theme/fontFallback.js.map +1 -0
- package/dist/theme/fonts.d.ts +11 -0
- package/dist/theme/fonts.js +16 -0
- package/dist/theme/fonts.js.map +1 -0
- package/dist/version.js +1 -1
- package/package.json +5 -4
|
@@ -4,7 +4,7 @@ import { useEffect, useState } from "react";
|
|
|
4
4
|
import { doc, getDoc } from "firebase/firestore";
|
|
5
5
|
import { getDb, getFirebaseApp } from "../../firebase/client.js";
|
|
6
6
|
import { callable } from "../../firebase/functionsClient.js";
|
|
7
|
-
import { Button, Card, Field, Input } from "../ui/primitives.js";
|
|
7
|
+
import { Button, Card, Field, Input, Textarea } from "../ui/primitives.js";
|
|
8
8
|
import { Switch } from "../ui/switch.js";
|
|
9
9
|
import { useToast } from "../ui/toast.js";
|
|
10
10
|
import { centsToEuroInput, eurosToCents } from "./lib.js";
|
|
@@ -16,11 +16,13 @@ function ShopSettingsTab({ siteId }) {
|
|
|
16
16
|
const [giftWrapInput, setGiftWrapInput] = useState("");
|
|
17
17
|
const [busy, setBusy] = useState(false);
|
|
18
18
|
const [zones, setZones] = useState([]);
|
|
19
|
+
const [profiel, setProfiel] = useState({});
|
|
19
20
|
useEffect(() => {
|
|
20
21
|
const db = getDb();
|
|
21
22
|
if (!db) return;
|
|
22
23
|
void getDoc(doc(db, "sites", siteId, "settings", "public")).then((snap) => {
|
|
23
24
|
const modules = snap.data()?.modules ?? {};
|
|
25
|
+
setProfiel(snap.data()?.business ?? {});
|
|
24
26
|
const entry = modules.shop;
|
|
25
27
|
const c = typeof entry === "object" && entry !== null ? entry.config ?? {} : {};
|
|
26
28
|
setCfg(c);
|
|
@@ -35,6 +37,9 @@ function ShopSettingsTab({ siteId }) {
|
|
|
35
37
|
free: z.freeFromCents ? centsToEuroInput(z.freeFromCents) : ""
|
|
36
38
|
}))
|
|
37
39
|
);
|
|
40
|
+
}).catch((e) => {
|
|
41
|
+
console.warn("[xicms] winkelinstellingen niet geladen:", e.message);
|
|
42
|
+
setCfg({});
|
|
38
43
|
});
|
|
39
44
|
}, [siteId]);
|
|
40
45
|
async function save() {
|
|
@@ -67,14 +72,25 @@ function ShopSettingsTab({ siteId }) {
|
|
|
67
72
|
}
|
|
68
73
|
if (!cfg) return /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx("p", { className: "text-sm text-slate-500", children: "Laden\u2026" }) });
|
|
69
74
|
const set = (k, v) => setCfg((p) => ({ ...p, [k]: v }));
|
|
75
|
+
const uitProfiel = {
|
|
76
|
+
businessName: profiel.name,
|
|
77
|
+
businessAddress: profiel.address?.street,
|
|
78
|
+
businessZip: profiel.address?.postalCode,
|
|
79
|
+
businessCity: profiel.address?.city,
|
|
80
|
+
businessKvk: profiel.kvk,
|
|
81
|
+
businessBtw: profiel.vat,
|
|
82
|
+
businessEmail: profiel.email,
|
|
83
|
+
businessPhone: profiel.phone
|
|
84
|
+
};
|
|
70
85
|
const ontbrekend = [
|
|
71
|
-
["bedrijfsnaam",
|
|
72
|
-
["adres",
|
|
73
|
-
["postcode",
|
|
74
|
-
["plaats",
|
|
75
|
-
["KVK-nummer",
|
|
76
|
-
["btw-nummer",
|
|
77
|
-
].filter(([,
|
|
86
|
+
["bedrijfsnaam", "businessName"],
|
|
87
|
+
["adres", "businessAddress"],
|
|
88
|
+
["postcode", "businessZip"],
|
|
89
|
+
["plaats", "businessCity"],
|
|
90
|
+
["KVK-nummer", "businessKvk"],
|
|
91
|
+
["btw-nummer", "businessBtw"]
|
|
92
|
+
].filter(([, k]) => !String(cfg[k] ?? "").trim() && !uitProfiel[k]?.trim()).map(([label]) => label);
|
|
93
|
+
const veld = (k) => ({ value: String(cfg[k] ?? ""), onChange: (e) => set(k, e.target.value), placeholder: uitProfiel[k] });
|
|
78
94
|
return /* @__PURE__ */ jsxs("div", { className: "grid gap-4 lg:grid-cols-2", children: [
|
|
79
95
|
/* @__PURE__ */ jsxs(Card, { children: [
|
|
80
96
|
/* @__PURE__ */ jsx("h3", { className: "mb-3 text-sm font-semibold", children: "Verzending & bestellen" }),
|
|
@@ -105,6 +121,29 @@ function ShopSettingsTab({ siteId }) {
|
|
|
105
121
|
] })
|
|
106
122
|
] })
|
|
107
123
|
] }),
|
|
124
|
+
/* @__PURE__ */ jsxs(Card, { children: [
|
|
125
|
+
/* @__PURE__ */ jsx("h3", { className: "mb-3 text-sm font-semibold", children: "Afhalen" }),
|
|
126
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-4", children: [
|
|
127
|
+
/* @__PURE__ */ jsx(
|
|
128
|
+
Switch,
|
|
129
|
+
{
|
|
130
|
+
checked: cfg.pickupEnabled ?? false,
|
|
131
|
+
onCheckedChange: (v) => setCfg((p) => ({ ...p, pickupEnabled: v })),
|
|
132
|
+
label: "Klanten kunnen kiezen voor afhalen (geen verzendkosten)"
|
|
133
|
+
}
|
|
134
|
+
),
|
|
135
|
+
cfg.pickupEnabled && /* @__PURE__ */ jsx(Field, { label: "Toelichting bij afhalen", hint: "Bijv. afhaaladres en tijden. De klant ziet dit bij het afrekenen en in de bevestigingsmail.", children: /* @__PURE__ */ jsx(
|
|
136
|
+
Textarea,
|
|
137
|
+
{
|
|
138
|
+
rows: 4,
|
|
139
|
+
maxLength: 1e3,
|
|
140
|
+
value: String(cfg.pickupNote ?? ""),
|
|
141
|
+
onChange: (e) => set("pickupNote", e.target.value),
|
|
142
|
+
placeholder: "Afhalen kan op afspraak aan de Voorbeeldstraat 1 in Plaats. Je krijgt een mail zodra je bestelling klaarstaat."
|
|
143
|
+
}
|
|
144
|
+
) })
|
|
145
|
+
] })
|
|
146
|
+
] }),
|
|
108
147
|
/* @__PURE__ */ jsxs(Card, { children: [
|
|
109
148
|
/* @__PURE__ */ jsx("h3", { className: "mb-3 text-sm font-semibold", children: "Cadeauverpakking" }),
|
|
110
149
|
/* @__PURE__ */ jsxs("div", { className: "grid gap-4", children: [
|
|
@@ -121,7 +160,7 @@ function ShopSettingsTab({ siteId }) {
|
|
|
121
160
|
] }),
|
|
122
161
|
/* @__PURE__ */ jsxs(Card, { children: [
|
|
123
162
|
/* @__PURE__ */ jsx("h3", { className: "mb-1 text-sm font-semibold", children: "Bedrijfsgegevens (factuur)" }),
|
|
124
|
-
/* @__PURE__ */ jsx("p", { className: "mb-3 text-xs text-slate-500", children: "Deze gegevens komen op de factuur-PDF die klanten bij hun bestelling krijgen." }),
|
|
163
|
+
/* @__PURE__ */ jsx("p", { className: "mb-3 text-xs text-slate-500", children: "Deze gegevens komen op de factuur-PDF die klanten bij hun bestelling krijgen. Laat je een veld leeg, dan gebruiken we wat er bij Instellingen \u2192 Bedrijf staat (grijs voorgevuld)." }),
|
|
125
164
|
ontbrekend.length > 0 && /* @__PURE__ */ jsxs("p", { className: "mb-3 rounded-lg bg-amber-50 px-3 py-2 text-sm text-amber-800", children: [
|
|
126
165
|
/* @__PURE__ */ jsx("strong", { children: "Nog invullen:" }),
|
|
127
166
|
" ",
|
|
@@ -129,19 +168,19 @@ function ShopSettingsTab({ siteId }) {
|
|
|
129
168
|
". Zonder deze gegevens staat er geen afzender op je factuur, en dan is hij voor de Belastingdienst niet geldig."
|
|
130
169
|
] }),
|
|
131
170
|
/* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
|
|
132
|
-
/* @__PURE__ */ jsx(Field, { label: "Bedrijfsnaam", children: /* @__PURE__ */ jsx(Input, {
|
|
133
|
-
/* @__PURE__ */ jsx(Field, { label: "Adres", children: /* @__PURE__ */ jsx(Input, {
|
|
171
|
+
/* @__PURE__ */ jsx(Field, { label: "Bedrijfsnaam", children: /* @__PURE__ */ jsx(Input, { ...veld("businessName") }) }),
|
|
172
|
+
/* @__PURE__ */ jsx(Field, { label: "Adres", children: /* @__PURE__ */ jsx(Input, { ...veld("businessAddress") }) }),
|
|
134
173
|
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[120px_1fr] gap-3", children: [
|
|
135
|
-
/* @__PURE__ */ jsx(Field, { label: "Postcode", children: /* @__PURE__ */ jsx(Input, {
|
|
136
|
-
/* @__PURE__ */ jsx(Field, { label: "Plaats", children: /* @__PURE__ */ jsx(Input, {
|
|
174
|
+
/* @__PURE__ */ jsx(Field, { label: "Postcode", children: /* @__PURE__ */ jsx(Input, { ...veld("businessZip") }) }),
|
|
175
|
+
/* @__PURE__ */ jsx(Field, { label: "Plaats", children: /* @__PURE__ */ jsx(Input, { ...veld("businessCity") }) })
|
|
137
176
|
] }),
|
|
138
177
|
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
|
|
139
|
-
/* @__PURE__ */ jsx(Field, { label: "KVK-nummer", children: /* @__PURE__ */ jsx(Input, {
|
|
140
|
-
/* @__PURE__ */ jsx(Field, { label: "Btw-nummer", children: /* @__PURE__ */ jsx(Input, {
|
|
178
|
+
/* @__PURE__ */ jsx(Field, { label: "KVK-nummer", children: /* @__PURE__ */ jsx(Input, { ...veld("businessKvk") }) }),
|
|
179
|
+
/* @__PURE__ */ jsx(Field, { label: "Btw-nummer", children: /* @__PURE__ */ jsx(Input, { ...veld("businessBtw") }) })
|
|
141
180
|
] }),
|
|
142
181
|
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
|
|
143
|
-
/* @__PURE__ */ jsx(Field, { label: "Contact-e-mail", hint: "Staat op de factuur en onderaan elke mail", children: /* @__PURE__ */ jsx(Input, {
|
|
144
|
-
/* @__PURE__ */ jsx(Field, { label: "Telefoon (optioneel)", children: /* @__PURE__ */ jsx(Input, {
|
|
182
|
+
/* @__PURE__ */ jsx(Field, { label: "Contact-e-mail", hint: "Staat op de factuur en onderaan elke mail", children: /* @__PURE__ */ jsx(Input, { ...veld("businessEmail") }) }),
|
|
183
|
+
/* @__PURE__ */ jsx(Field, { label: "Telefoon (optioneel)", children: /* @__PURE__ */ jsx(Input, { ...veld("businessPhone") }) })
|
|
145
184
|
] })
|
|
146
185
|
] })
|
|
147
186
|
] }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/shop/ShopSettingsTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { doc, getDoc } from \"firebase/firestore\";\nimport { getDb, getFirebaseApp } from \"../../firebase/client\";\nimport { callable } from \"../../firebase/functionsClient\";\nimport { Button, Card, Field, Input } from \"../ui/primitives\";\nimport { Switch } from \"../ui/switch\";\nimport { useToast } from \"../ui/toast\";\nimport { centsToEuroInput, eurosToCents } from \"./lib\";\n\n/**\n * Winkel-instellingen — leest/schrijft DEZELFDE module-config als Instellingen → Modules\n * (settings/public.modules.shop.config; geen tweede bron van waarheid), maar met winkel-\n * presentatie: bedragen in euro's en uitleg per veld. Siteadmin-only (rules).\n */\ninterface ShopCfg {\n shippingFlatCents?: number;\n shippingFreeFromCents?: number;\n orderPrefix?: string;\n currency?: string;\n businessName?: string;\n businessAddress?: string;\n businessZip?: string;\n businessCity?: string;\n businessKvk?: string;\n businessBtw?: string;\n giftWrapEnabled?: boolean;\n giftWrapPriceCents?: number;\n [k: string]: unknown;\n}\n\nexport function ShopSettingsTab({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const [cfg, setCfg] = useState<ShopCfg | null>(null);\n const [shipInput, setShipInput] = useState(\"\");\n const [freeInput, setFreeInput] = useState(\"\");\n const [giftWrapInput, setGiftWrapInput] = useState(\"\");\n const [busy, setBusy] = useState(false);\n const [zones, setZones] = useState<{ naam: string; landen: string; flat: string; free: string }[]>([]);\n\n useEffect(() => {\n const db = getDb();\n if (!db) return;\n void getDoc(doc(db, \"sites\", siteId, \"settings\", \"public\")).then((snap) => {\n const modules = (snap.data()?.modules ?? {}) as Record<string, { config?: ShopCfg } | boolean>;\n const entry = modules.shop;\n const c = typeof entry === \"object\" && entry !== null ? (entry.config ?? {}) : {};\n setCfg(c);\n setShipInput(centsToEuroInput(c.shippingFlatCents ?? 495));\n setFreeInput(c.shippingFreeFromCents ? centsToEuroInput(c.shippingFreeFromCents) : \"\");\n setGiftWrapInput(centsToEuroInput(c.giftWrapPriceCents ?? 0));\n setZones(\n ((c.shippingZones as { naam: string; landen?: string[]; flatCents: number; freeFromCents?: number }[] | undefined) ?? []).map((z) => ({\n naam: z.naam,\n landen: (z.landen ?? []).join(\", \"),\n flat: centsToEuroInput(z.flatCents),\n free: z.freeFromCents ? centsToEuroInput(z.freeFromCents) : \"\",\n })),\n );\n });\n }, [siteId]);\n\n async function save() {\n const app = getFirebaseApp();\n if (!app || !cfg) return;\n setBusy(true);\n try {\n const next: ShopCfg = {\n ...cfg,\n shippingFlatCents: eurosToCents(shipInput) ?? 495,\n shippingFreeFromCents: freeInput.trim() === \"\" ? 0 : (eurosToCents(freeInput) ?? 0),\n giftWrapPriceCents: eurosToCents(giftWrapInput) ?? 0,\n shippingZones: zones\n .filter((z) => z.naam.trim())\n .map((z) => ({\n naam: z.naam.trim(),\n // \"BE, DE\" → [\"BE\",\"DE\"]; leeg = de \"overige landen\"-zone.\n landen: z.landen.split(/[,;\\s]+/).map((l) => l.trim().toUpperCase()).filter(Boolean).slice(0, 60),\n flatCents: eurosToCents(z.flat) ?? 0,\n freeFromCents: z.free.trim() === \"\" ? 0 : (eurosToCents(z.free) ?? 0),\n })),\n };\n /**\n * Via de saveSiteSettings-callable, NIET rechtstreeks naar Firestore. Deze tab deed nog een\n * directe setDoc op settings/public, maar die deur zit sinds de settings-verstrakking in de\n * rules voor iederéén dicht (ook superadmin) — de klant kreeg letterlijk \"Missing or\n * insufficient permissions\" bij het opslaan van verzendkosten. De callable is bovendien de\n * enige route die site/public consistent houdt én de cache-tag pusht, zodat de nieuwe\n * verzendkosten binnen seconden op de live winkel staan i.p.v. na het ISR-vangnet.\n * set+merge in de callable merged diep: de enabled-vlag en andere modules blijven staan.\n */\n const fn = callable(app, \"saveSiteSettings\");\n await fn({ siteId, settings: { modules: { shop: { config: next } } } });\n setCfg(next);\n toast({ title: \"Instellingen opgeslagen\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Opslaan mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n if (!cfg) return <Card><p className=\"text-sm text-slate-500\">Laden…</p></Card>;\n\n const set = (k: string, v: string) => setCfg((p) => ({ ...p, [k]: v }));\n\n /** Wettelijk minimum op een factuur: wie ben je, waar zit je, en onder welke nummers. */\n const ontbrekend = (\n [\n [\"bedrijfsnaam\", cfg.businessName],\n [\"adres\", cfg.businessAddress],\n [\"postcode\", cfg.businessZip],\n [\"plaats\", cfg.businessCity],\n [\"KVK-nummer\", cfg.businessKvk],\n [\"btw-nummer\", cfg.businessBtw],\n ] as [string, unknown][]\n )\n .filter(([, v]) => !String(v ?? \"\").trim())\n .map(([label]) => label);\n\n return (\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <Card>\n <h3 className=\"mb-3 text-sm font-semibold\">Verzending & bestellen</h3>\n <div className=\"grid gap-4\">\n <Field label=\"Verzendkosten (€)\" hint=\"Vast bedrag per bestelling binnen Nederland\">\n <Input value={shipInput} onChange={(e) => setShipInput(e.target.value)} placeholder=\"4,95\" className=\"w-32\" />\n </Field>\n <Field label=\"Gratis verzending vanaf (€)\" hint=\"Leeg = nooit gratis\">\n <Input value={freeInput} onChange={(e) => setFreeInput(e.target.value)} placeholder=\"50,00\" className=\"w-32\" />\n </Field>\n <Field label=\"Ordernummer-prefix\" hint=\"Bijv. CKC → CKC-2026-0001\">\n <Input value={String(cfg.orderPrefix ?? \"ORDER\")} onChange={(e) => set(\"orderPrefix\", e.target.value.toUpperCase().slice(0, 12))} className=\"w-32\" />\n </Field>\n\n {/* Zones: pas nodig zodra je buiten Nederland verzendt. Zonder zones geldt het tarief\n hierboven voor alles — precies zoals het altijd werkte. */}\n <div className=\"border-t border-slate-100 pt-4\">\n <p className=\"text-sm font-medium text-slate-800\">Verzenden naar het buitenland</p>\n <p className=\"mt-1 mb-3 text-xs text-slate-500\">\n Zonder zones geldt het tarief hierboven voor élke bestelling. Voeg een zone toe om per\n land een eigen tarief te rekenen. Een zone <strong>zonder landen</strong> is \"alle\n overige landen\"; laat je die weg, dan verzend je alléén naar de genoemde landen — de\n afrekenpagina toont dan precies die keuze.\n </p>\n <div className=\"grid gap-2\">\n {zones.map((z, i) => {\n const zet = (patch: Partial<(typeof zones)[number]>) =>\n setZones((p) => p.map((x, j) => (j === i ? { ...x, ...patch } : x)));\n return (\n <div key={i} className=\"grid grid-cols-[1fr_1fr_90px_90px_auto] items-center gap-2\">\n <Input value={z.naam} onChange={(e) => zet({ naam: e.target.value })} placeholder=\"Naam (bv. België)\" />\n <Input value={z.landen} onChange={(e) => zet({ landen: e.target.value })} placeholder=\"BE, DE (leeg = overige)\" />\n <Input value={z.flat} onChange={(e) => zet({ flat: e.target.value })} placeholder=\"Tarief €\" />\n <Input value={z.free} onChange={(e) => zet({ free: e.target.value })} placeholder=\"Gratis v.a. €\" />\n <Button size=\"sm\" variant=\"ghost\" onClick={() => setZones((p) => p.filter((_, j) => j !== i))}>✕</Button>\n </div>\n );\n })}\n <div>\n <Button size=\"sm\" variant=\"secondary\" disabled={zones.length >= 10} onClick={() => setZones((p) => [...p, { naam: \"\", landen: \"\", flat: \"\", free: \"\" }])}>\n + Zone\n </Button>\n </div>\n </div>\n </div>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-3 text-sm font-semibold\">Cadeauverpakking</h3>\n <div className=\"grid gap-4\">\n <Switch\n checked={cfg.giftWrapEnabled ?? false}\n onCheckedChange={(v) => setCfg((p) => ({ ...p, giftWrapEnabled: v }))}\n label=\"Cadeauverpakking aanbieden bij het afrekenen\"\n />\n {cfg.giftWrapEnabled && (\n <Field label=\"Meerprijs (€)\" hint=\"Komt als aparte regel op de bestelling en de factuur; de klant kan een kaartjestekst + opmerking meegeven\">\n <Input value={giftWrapInput} onChange={(e) => setGiftWrapInput(e.target.value)} placeholder=\"3,95\" className=\"w-32\" />\n </Field>\n )}\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-sm font-semibold\">Bedrijfsgegevens (factuur)</h3>\n <p className=\"mb-3 text-xs text-slate-500\">Deze gegevens komen op de factuur-PDF die klanten bij hun bestelling krijgen.</p>\n {/* Zonder deze gegevens gaat er wél een factuur uit, maar zonder afzender — en een factuur\n zonder bedrijfsnaam, adres en KVK/btw is fiscaal niet geldig. Dat gebeurde stil. */}\n {ontbrekend.length > 0 && (\n <p className=\"mb-3 rounded-lg bg-amber-50 px-3 py-2 text-sm text-amber-800\">\n <strong>Nog invullen:</strong> {ontbrekend.join(\", \")}. Zonder deze gegevens staat er geen\n afzender op je factuur, en dan is hij voor de Belastingdienst niet geldig.\n </p>\n )}\n <div className=\"grid gap-3\">\n <Field label=\"Bedrijfsnaam\"><Input value={String(cfg.businessName ?? \"\")} onChange={(e) => set(\"businessName\", e.target.value)} /></Field>\n <Field label=\"Adres\"><Input value={String(cfg.businessAddress ?? \"\")} onChange={(e) => set(\"businessAddress\", e.target.value)} /></Field>\n <div className=\"grid grid-cols-[120px_1fr] gap-3\">\n <Field label=\"Postcode\"><Input value={String(cfg.businessZip ?? \"\")} onChange={(e) => set(\"businessZip\", e.target.value)} /></Field>\n <Field label=\"Plaats\"><Input value={String(cfg.businessCity ?? \"\")} onChange={(e) => set(\"businessCity\", e.target.value)} /></Field>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <Field label=\"KVK-nummer\"><Input value={String(cfg.businessKvk ?? \"\")} onChange={(e) => set(\"businessKvk\", e.target.value)} /></Field>\n <Field label=\"Btw-nummer\"><Input value={String(cfg.businessBtw ?? \"\")} onChange={(e) => set(\"businessBtw\", e.target.value)} /></Field>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <Field label=\"Contact-e-mail\" hint=\"Staat op de factuur en onderaan elke mail\">\n <Input value={String(cfg.businessEmail ?? \"\")} onChange={(e) => set(\"businessEmail\", e.target.value)} />\n </Field>\n <Field label=\"Telefoon (optioneel)\">\n <Input value={String(cfg.businessPhone ?? \"\")} onChange={(e) => set(\"businessPhone\", e.target.value)} />\n </Field>\n </div>\n </div>\n </Card>\n\n <div className=\"lg:col-span-2\">\n <Button onClick={save} disabled={busy}>Opslaan</Button>\n <p className=\"mt-2 text-xs text-slate-500\">\n Paden en valuta van de winkel staan onder Instellingen → Modules → Webwinkel.\n </p>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/shop/ShopSettingsTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { doc, getDoc } from \"firebase/firestore\";\nimport { getDb, getFirebaseApp } from \"../../firebase/client\";\nimport { callable } from \"../../firebase/functionsClient\";\nimport { Button, Card, Field, Input, Textarea } from \"../ui/primitives\";\nimport { Switch } from \"../ui/switch\";\nimport { useToast } from \"../ui/toast\";\nimport { centsToEuroInput, eurosToCents } from \"./lib\";\nimport type { Business } from \"../../schema/business\";\n\n/**\n * Winkel-instellingen — leest/schrijft DEZELFDE module-config als Instellingen → Modules\n * (settings/public.modules.shop.config; geen tweede bron van waarheid), maar met winkel-\n * presentatie: bedragen in euro's en uitleg per veld. Siteadmin-only (rules).\n */\ninterface ShopCfg {\n shippingFlatCents?: number;\n shippingFreeFromCents?: number;\n orderPrefix?: string;\n currency?: string;\n businessName?: string;\n businessAddress?: string;\n businessZip?: string;\n businessCity?: string;\n businessKvk?: string;\n businessBtw?: string;\n giftWrapEnabled?: boolean;\n giftWrapPriceCents?: number;\n pickupEnabled?: boolean;\n pickupNote?: string;\n [k: string]: unknown;\n}\n\nexport function ShopSettingsTab({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const [cfg, setCfg] = useState<ShopCfg | null>(null);\n const [shipInput, setShipInput] = useState(\"\");\n const [freeInput, setFreeInput] = useState(\"\");\n const [giftWrapInput, setGiftWrapInput] = useState(\"\");\n const [busy, setBusy] = useState(false);\n const [zones, setZones] = useState<{ naam: string; landen: string; flat: string; free: string }[]>([]);\n const [profiel, setProfiel] = useState<Business>({});\n\n useEffect(() => {\n const db = getDb();\n if (!db) return;\n void getDoc(doc(db, \"sites\", siteId, \"settings\", \"public\")).then((snap) => {\n const modules = (snap.data()?.modules ?? {}) as Record<string, { config?: ShopCfg } | boolean>;\n setProfiel((snap.data()?.business ?? {}) as Business);\n const entry = modules.shop;\n const c = typeof entry === \"object\" && entry !== null ? (entry.config ?? {}) : {};\n setCfg(c);\n setShipInput(centsToEuroInput(c.shippingFlatCents ?? 495));\n setFreeInput(c.shippingFreeFromCents ? centsToEuroInput(c.shippingFreeFromCents) : \"\");\n setGiftWrapInput(centsToEuroInput(c.giftWrapPriceCents ?? 0));\n setZones(\n ((c.shippingZones as { naam: string; landen?: string[]; flatCents: number; freeFromCents?: number }[] | undefined) ?? []).map((z) => ({\n naam: z.naam,\n landen: (z.landen ?? []).join(\", \"),\n flat: centsToEuroInput(z.flatCents),\n free: z.freeFromCents ? centsToEuroInput(z.freeFromCents) : \"\",\n })),\n );\n }).catch((e) => {\n // 8.2: zonder catch bleef dit tabblad eeuwig op \"Laden…\" staan bij een leesfout.\n console.warn(\"[xicms] winkelinstellingen niet geladen:\", (e as Error).message);\n setCfg({});\n });\n }, [siteId]);\n\n async function save() {\n const app = getFirebaseApp();\n if (!app || !cfg) return;\n setBusy(true);\n try {\n const next: ShopCfg = {\n ...cfg,\n shippingFlatCents: eurosToCents(shipInput) ?? 495,\n shippingFreeFromCents: freeInput.trim() === \"\" ? 0 : (eurosToCents(freeInput) ?? 0),\n giftWrapPriceCents: eurosToCents(giftWrapInput) ?? 0,\n shippingZones: zones\n .filter((z) => z.naam.trim())\n .map((z) => ({\n naam: z.naam.trim(),\n // \"BE, DE\" → [\"BE\",\"DE\"]; leeg = de \"overige landen\"-zone.\n landen: z.landen.split(/[,;\\s]+/).map((l) => l.trim().toUpperCase()).filter(Boolean).slice(0, 60),\n flatCents: eurosToCents(z.flat) ?? 0,\n freeFromCents: z.free.trim() === \"\" ? 0 : (eurosToCents(z.free) ?? 0),\n })),\n };\n /**\n * Via de saveSiteSettings-callable, NIET rechtstreeks naar Firestore. Deze tab deed nog een\n * directe setDoc op settings/public, maar die deur zit sinds de settings-verstrakking in de\n * rules voor iederéén dicht (ook superadmin) — de klant kreeg letterlijk \"Missing or\n * insufficient permissions\" bij het opslaan van verzendkosten. De callable is bovendien de\n * enige route die site/public consistent houdt én de cache-tag pusht, zodat de nieuwe\n * verzendkosten binnen seconden op de live winkel staan i.p.v. na het ISR-vangnet.\n * set+merge in de callable merged diep: de enabled-vlag en andere modules blijven staan.\n */\n const fn = callable(app, \"saveSiteSettings\");\n await fn({ siteId, settings: { modules: { shop: { config: next } } } });\n setCfg(next);\n toast({ title: \"Instellingen opgeslagen\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Opslaan mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n if (!cfg) return <Card><p className=\"text-sm text-slate-500\">Laden…</p></Card>;\n\n const set = (k: string, v: string) => setCfg((p) => ({ ...p, [k]: v }));\n\n /**\n * Wettelijk minimum op een factuur: wie ben je, waar zit je, en onder welke nummers. 8.16: een\n * leeg veld hier valt op de factuur terug op Instellingen → Bedrijf (functions `bedrijfVoorMail`),\n * dus telt het pas als ontbrekend als het daar ook leeg is. Het profiel staat als voorbeeldtekst.\n */\n const uitProfiel: Record<string, string | undefined> = {\n businessName: profiel.name,\n businessAddress: profiel.address?.street,\n businessZip: profiel.address?.postalCode,\n businessCity: profiel.address?.city,\n businessKvk: profiel.kvk,\n businessBtw: profiel.vat,\n businessEmail: profiel.email,\n businessPhone: profiel.phone,\n };\n const ontbrekend = (\n [\n [\"bedrijfsnaam\", \"businessName\"],\n [\"adres\", \"businessAddress\"],\n [\"postcode\", \"businessZip\"],\n [\"plaats\", \"businessCity\"],\n [\"KVK-nummer\", \"businessKvk\"],\n [\"btw-nummer\", \"businessBtw\"],\n ] as [string, string][]\n )\n .filter(([, k]) => !String(cfg[k] ?? \"\").trim() && !uitProfiel[k]?.trim())\n .map(([label]) => label);\n const veld = (k: string) => ({ value: String(cfg[k] ?? \"\"), onChange: (e: { target: { value: string } }) => set(k, e.target.value), placeholder: uitProfiel[k] });\n\n return (\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <Card>\n <h3 className=\"mb-3 text-sm font-semibold\">Verzending & bestellen</h3>\n <div className=\"grid gap-4\">\n <Field label=\"Verzendkosten (€)\" hint=\"Vast bedrag per bestelling binnen Nederland\">\n <Input value={shipInput} onChange={(e) => setShipInput(e.target.value)} placeholder=\"4,95\" className=\"w-32\" />\n </Field>\n <Field label=\"Gratis verzending vanaf (€)\" hint=\"Leeg = nooit gratis\">\n <Input value={freeInput} onChange={(e) => setFreeInput(e.target.value)} placeholder=\"50,00\" className=\"w-32\" />\n </Field>\n <Field label=\"Ordernummer-prefix\" hint=\"Bijv. CKC → CKC-2026-0001\">\n <Input value={String(cfg.orderPrefix ?? \"ORDER\")} onChange={(e) => set(\"orderPrefix\", e.target.value.toUpperCase().slice(0, 12))} className=\"w-32\" />\n </Field>\n\n {/* Zones: pas nodig zodra je buiten Nederland verzendt. Zonder zones geldt het tarief\n hierboven voor alles — precies zoals het altijd werkte. */}\n <div className=\"border-t border-slate-100 pt-4\">\n <p className=\"text-sm font-medium text-slate-800\">Verzenden naar het buitenland</p>\n <p className=\"mt-1 mb-3 text-xs text-slate-500\">\n Zonder zones geldt het tarief hierboven voor élke bestelling. Voeg een zone toe om per\n land een eigen tarief te rekenen. Een zone <strong>zonder landen</strong> is \"alle\n overige landen\"; laat je die weg, dan verzend je alléén naar de genoemde landen — de\n afrekenpagina toont dan precies die keuze.\n </p>\n <div className=\"grid gap-2\">\n {zones.map((z, i) => {\n const zet = (patch: Partial<(typeof zones)[number]>) =>\n setZones((p) => p.map((x, j) => (j === i ? { ...x, ...patch } : x)));\n return (\n <div key={i} className=\"grid grid-cols-[1fr_1fr_90px_90px_auto] items-center gap-2\">\n <Input value={z.naam} onChange={(e) => zet({ naam: e.target.value })} placeholder=\"Naam (bv. België)\" />\n <Input value={z.landen} onChange={(e) => zet({ landen: e.target.value })} placeholder=\"BE, DE (leeg = overige)\" />\n <Input value={z.flat} onChange={(e) => zet({ flat: e.target.value })} placeholder=\"Tarief €\" />\n <Input value={z.free} onChange={(e) => zet({ free: e.target.value })} placeholder=\"Gratis v.a. €\" />\n <Button size=\"sm\" variant=\"ghost\" onClick={() => setZones((p) => p.filter((_, j) => j !== i))}>✕</Button>\n </div>\n );\n })}\n <div>\n <Button size=\"sm\" variant=\"secondary\" disabled={zones.length >= 10} onClick={() => setZones((p) => [...p, { naam: \"\", landen: \"\", flat: \"\", free: \"\" }])}>\n + Zone\n </Button>\n </div>\n </div>\n </div>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-3 text-sm font-semibold\">Afhalen</h3>\n <div className=\"grid gap-4\">\n <Switch\n checked={cfg.pickupEnabled ?? false}\n onCheckedChange={(v) => setCfg((p) => ({ ...p, pickupEnabled: v }))}\n label=\"Klanten kunnen kiezen voor afhalen (geen verzendkosten)\"\n />\n {cfg.pickupEnabled && (\n <Field label=\"Toelichting bij afhalen\" hint=\"Bijv. afhaaladres en tijden. De klant ziet dit bij het afrekenen en in de bevestigingsmail.\">\n <Textarea\n rows={4}\n maxLength={1000}\n value={String(cfg.pickupNote ?? \"\")}\n onChange={(e) => set(\"pickupNote\", e.target.value)}\n placeholder=\"Afhalen kan op afspraak aan de Voorbeeldstraat 1 in Plaats. Je krijgt een mail zodra je bestelling klaarstaat.\"\n />\n </Field>\n )}\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-3 text-sm font-semibold\">Cadeauverpakking</h3>\n <div className=\"grid gap-4\">\n <Switch\n checked={cfg.giftWrapEnabled ?? false}\n onCheckedChange={(v) => setCfg((p) => ({ ...p, giftWrapEnabled: v }))}\n label=\"Cadeauverpakking aanbieden bij het afrekenen\"\n />\n {cfg.giftWrapEnabled && (\n <Field label=\"Meerprijs (€)\" hint=\"Komt als aparte regel op de bestelling en de factuur; de klant kan een kaartjestekst + opmerking meegeven\">\n <Input value={giftWrapInput} onChange={(e) => setGiftWrapInput(e.target.value)} placeholder=\"3,95\" className=\"w-32\" />\n </Field>\n )}\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-sm font-semibold\">Bedrijfsgegevens (factuur)</h3>\n <p className=\"mb-3 text-xs text-slate-500\">\n Deze gegevens komen op de factuur-PDF die klanten bij hun bestelling krijgen. Laat je een veld leeg, dan\n gebruiken we wat er bij Instellingen → Bedrijf staat (grijs voorgevuld).\n </p>\n {/* Zonder deze gegevens gaat er wél een factuur uit, maar zonder afzender — en een factuur\n zonder bedrijfsnaam, adres en KVK/btw is fiscaal niet geldig. Dat gebeurde stil. */}\n {ontbrekend.length > 0 && (\n <p className=\"mb-3 rounded-lg bg-amber-50 px-3 py-2 text-sm text-amber-800\">\n <strong>Nog invullen:</strong> {ontbrekend.join(\", \")}. Zonder deze gegevens staat er geen\n afzender op je factuur, en dan is hij voor de Belastingdienst niet geldig.\n </p>\n )}\n <div className=\"grid gap-3\">\n <Field label=\"Bedrijfsnaam\"><Input {...veld(\"businessName\")} /></Field>\n <Field label=\"Adres\"><Input {...veld(\"businessAddress\")} /></Field>\n <div className=\"grid grid-cols-[120px_1fr] gap-3\">\n <Field label=\"Postcode\"><Input {...veld(\"businessZip\")} /></Field>\n <Field label=\"Plaats\"><Input {...veld(\"businessCity\")} /></Field>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <Field label=\"KVK-nummer\"><Input {...veld(\"businessKvk\")} /></Field>\n <Field label=\"Btw-nummer\"><Input {...veld(\"businessBtw\")} /></Field>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <Field label=\"Contact-e-mail\" hint=\"Staat op de factuur en onderaan elke mail\">\n <Input {...veld(\"businessEmail\")} />\n </Field>\n <Field label=\"Telefoon (optioneel)\">\n <Input {...veld(\"businessPhone\")} />\n </Field>\n </div>\n </div>\n </Card>\n\n <div className=\"lg:col-span-2\">\n <Button onClick={save} disabled={busy}>Opslaan</Button>\n <p className=\"mt-2 text-xs text-slate-500\">\n Paden en valuta van de winkel staan onder Instellingen → Modules → Webwinkel.\n </p>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -9,9 +9,13 @@ function subscribeProduct(siteId, productId, cb) {
|
|
|
9
9
|
return () => {
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
|
-
return onSnapshot(
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
return onSnapshot(
|
|
13
|
+
doc(db, "sites", siteId, "products", productId),
|
|
14
|
+
(snap) => {
|
|
15
|
+
cb(snap.exists() ? { id: snap.id, ...snap.data() } : null);
|
|
16
|
+
},
|
|
17
|
+
(e) => console.warn("[xicms] live-lijst niet geladen:", e.message)
|
|
18
|
+
);
|
|
15
19
|
}
|
|
16
20
|
const saveProductCallable = shopCallable("saveProduct");
|
|
17
21
|
const deleteProductCallable = shopCallable("deleteProduct");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/shop/productClient.ts"],"sourcesContent":["\"use client\";\n\nimport { doc, getDoc, onSnapshot, setDoc, updateDoc, deleteDoc } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport { shopCallable } from \"./lib\";\nimport type { Product } from \"../../schema/shop\";\n\nexport type StoredProduct = Product & { id: string };\n\n/**\n * Eén product, live — voor het paneel dat de bewerkmodus opent bij een productblok.\n *\n * Een abonnement en geen eenmalige lezing: hetzelfde product kan tegelijk openstaan in het\n * Webwinkel-paneel of in een tweede tabblad, en dan hoort dit paneel niet met een oude prijs te\n * blijven staan.\n */\nexport function subscribeProduct(\n siteId: string,\n productId: string,\n cb: (p: StoredProduct | null) => void,\n): () => void {\n const db = getDb();\n if (!db) {\n cb(null);\n return () => {};\n }\n return onSnapshot(doc(db, \"sites\", siteId, \"products\", productId)
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/shop/productClient.ts"],"sourcesContent":["\"use client\";\n\nimport { doc, getDoc, onSnapshot, setDoc, updateDoc, deleteDoc } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport { shopCallable } from \"./lib\";\nimport type { Product } from \"../../schema/shop\";\n\nexport type StoredProduct = Product & { id: string };\n\n/**\n * Eén product, live — voor het paneel dat de bewerkmodus opent bij een productblok.\n *\n * Een abonnement en geen eenmalige lezing: hetzelfde product kan tegelijk openstaan in het\n * Webwinkel-paneel of in een tweede tabblad, en dan hoort dit paneel niet met een oude prijs te\n * blijven staan.\n */\nexport function subscribeProduct(\n siteId: string,\n productId: string,\n cb: (p: StoredProduct | null) => void,\n): () => void {\n const db = getDb();\n if (!db) {\n cb(null);\n return () => {};\n }\n return onSnapshot(\n doc(db, \"sites\", siteId, \"products\", productId),\n (snap) => {\n cb(snap.exists() ? ({ id: snap.id, ...(snap.data() as Product) }) : null);\n },\n (e) => console.warn(\"[xicms] live-lijst niet geladen:\", e.message),\n );\n}\n\n/**\n * Alle product-writes lopen sinds 7.9 door de `saveProduct`-callable — niet om de rechten (die\n * regelen de rules ook), maar om de CACHE. De shoppagina's bewaren hun server-reads onder de tag\n * `xicms:{site}:products`, en alleen de server kan die tag na een write ongeldig verklaren. Een\n * rechtstreekse client-write stond daardoor meteen in Firestore en pas na het ISR-vangnet (tot\n * een uur) op de site — voor een winkel is een uur oude prijs geen detail. Zelfde les als bij\n * news/symbolen/instellingen (SITE-AUDIT §11); producten waren de vergeten laatste.\n *\n * ── DE TERUGVAL ────────────────────────────────────────────────────────────────────────────\n *\n * Core (npm) en functions (aparte deploy) bewegen niet in lockstep. Draait er nog een backend\n * zonder `saveProduct`, dan antwoordt die \"not-found\" — en dan is de rechtstreekse write het oude,\n * werkende gedrag in plaats van een kapotte opslaan-knop. De cache-ping ontbreekt dan, precies\n * zoals vóór 7.9. De terugval kan weg zodra alle omgevingen op functions ≥ 3.11 staan.\n */\nconst saveProductCallable = shopCallable<\n { siteId: string; productId?: string; patch?: boolean; product: Record<string, unknown> },\n { id: string }\n>(\"saveProduct\");\nconst deleteProductCallable = shopCallable<{ siteId: string; productId: string }, { message: string }>(\"deleteProduct\");\n\nfunction isMissingCallable(err: unknown): boolean {\n return (err as { code?: string } | null)?.code === \"functions/not-found\";\n}\n\n/**\n * Volledige save (het formulier): VERVANGT het document, zodat een gewiste variant ook echt weg is.\n */\nexport async function saveProductDoc(siteId: string, productId: string | undefined, product: Product): Promise<string> {\n try {\n const res = await saveProductCallable({ siteId, productId, product: product as unknown as Record<string, unknown> });\n return res.id;\n } catch (err) {\n if (!isMissingCallable(err)) throw err;\n const db = getDb();\n if (!db) throw new Error(\"Firebase niet geconfigureerd.\");\n if (productId) {\n await setDoc(doc(db, \"sites\", siteId, \"products\", productId), product);\n return productId;\n }\n const ref = doc(db, \"sites\", siteId, \"products\", crypto.randomUUID());\n await setDoc(ref, product);\n return ref.id;\n }\n}\n\n/**\n * Eén veld (het inline potlood op de pagina) — een patch, zodat de rest van het product met al\n * zijn velden (varianten, voorraad, personalisatie) onaangeroerd blijft.\n *\n * Een blokveld gaat naar de WERKVERSIE van die ene pagina en wordt pas zichtbaar als je\n * publiceert. Een product heeft geen werkversie: wie hier de naam wijzigt, wijzigt hem METEEN en\n * OVERAL. Dat staat expliciet in de tekst bij het potlood, zodat het een keuze is en geen\n * verrassing.\n */\nexport async function setProductField(\n siteId: string,\n productId: string,\n field: string,\n value: unknown,\n): Promise<void> {\n try {\n await saveProductCallable({ siteId, productId, patch: true, product: { [field]: value } });\n } catch (err) {\n if (!isMissingCallable(err)) throw err;\n const db = getDb();\n if (!db) throw new Error(\"Firebase niet geconfigureerd.\");\n await updateDoc(doc(db, \"sites\", siteId, \"products\", productId), { [field]: value });\n }\n}\n\n/** Verwijderen — zelfde route, zelfde terugval. */\nexport async function removeProduct(siteId: string, productId: string): Promise<void> {\n try {\n await deleteProductCallable({ siteId, productId });\n } catch (err) {\n if (!isMissingCallable(err)) throw err;\n const db = getDb();\n if (!db) throw new Error(\"Firebase niet geconfigureerd.\");\n await deleteDoc(doc(db, \"sites\", siteId, \"products\", productId));\n }\n}\n\n/** Eenmalige lezing — voor het moment waarop de editor moet weten wat er nú staat. */\nexport async function getProduct(siteId: string, productId: string): Promise<StoredProduct | null> {\n const db = getDb();\n if (!db) return null;\n const snap = await getDoc(doc(db, \"sites\", siteId, \"products\", productId));\n return snap.exists() ? ({ id: snap.id, ...(snap.data() as Product) }) : null;\n}\n"],"mappings":";AAEA,SAAS,KAAK,QAAQ,YAAY,QAAQ,WAAW,iBAAiB;AACtE,SAAS,aAAa;AACtB,SAAS,oBAAoB;AAYtB,SAAS,iBACd,QACA,WACA,IACY;AACZ,QAAM,KAAK,MAAM;AACjB,MAAI,CAAC,IAAI;AACP,OAAG,IAAI;AACP,WAAO,MAAM;AAAA,IAAC;AAAA,EAChB;AACA,SAAO;AAAA,IACL,IAAI,IAAI,SAAS,QAAQ,YAAY,SAAS;AAAA,IAC9C,CAAC,SAAS;AACR,SAAG,KAAK,OAAO,IAAK,EAAE,IAAI,KAAK,IAAI,GAAI,KAAK,KAAK,EAAc,IAAK,IAAI;AAAA,IAC1E;AAAA,IACA,CAAC,MAAM,QAAQ,KAAK,oCAAoC,EAAE,OAAO;AAAA,EACnE;AACF;AAiBA,MAAM,sBAAsB,aAG1B,aAAa;AACf,MAAM,wBAAwB,aAAyE,eAAe;AAEtH,SAAS,kBAAkB,KAAuB;AAChD,SAAQ,KAAkC,SAAS;AACrD;AAKA,eAAsB,eAAe,QAAgB,WAA+B,SAAmC;AACrH,MAAI;AACF,UAAM,MAAM,MAAM,oBAAoB,EAAE,QAAQ,WAAW,QAAuD,CAAC;AACnH,WAAO,IAAI;AAAA,EACb,SAAS,KAAK;AACZ,QAAI,CAAC,kBAAkB,GAAG,EAAG,OAAM;AACnC,UAAM,KAAK,MAAM;AACjB,QAAI,CAAC,GAAI,OAAM,IAAI,MAAM,+BAA+B;AACxD,QAAI,WAAW;AACb,YAAM,OAAO,IAAI,IAAI,SAAS,QAAQ,YAAY,SAAS,GAAG,OAAO;AACrE,aAAO;AAAA,IACT;AACA,UAAM,MAAM,IAAI,IAAI,SAAS,QAAQ,YAAY,OAAO,WAAW,CAAC;AACpE,UAAM,OAAO,KAAK,OAAO;AACzB,WAAO,IAAI;AAAA,EACb;AACF;AAWA,eAAsB,gBACpB,QACA,WACA,OACA,OACe;AACf,MAAI;AACF,UAAM,oBAAoB,EAAE,QAAQ,WAAW,OAAO,MAAM,SAAS,EAAE,CAAC,KAAK,GAAG,MAAM,EAAE,CAAC;AAAA,EAC3F,SAAS,KAAK;AACZ,QAAI,CAAC,kBAAkB,GAAG,EAAG,OAAM;AACnC,UAAM,KAAK,MAAM;AACjB,QAAI,CAAC,GAAI,OAAM,IAAI,MAAM,+BAA+B;AACxD,UAAM,UAAU,IAAI,IAAI,SAAS,QAAQ,YAAY,SAAS,GAAG,EAAE,CAAC,KAAK,GAAG,MAAM,CAAC;AAAA,EACrF;AACF;AAGA,eAAsB,cAAc,QAAgB,WAAkC;AACpF,MAAI;AACF,UAAM,sBAAsB,EAAE,QAAQ,UAAU,CAAC;AAAA,EACnD,SAAS,KAAK;AACZ,QAAI,CAAC,kBAAkB,GAAG,EAAG,OAAM;AACnC,UAAM,KAAK,MAAM;AACjB,QAAI,CAAC,GAAI,OAAM,IAAI,MAAM,+BAA+B;AACxD,UAAM,UAAU,IAAI,IAAI,SAAS,QAAQ,YAAY,SAAS,CAAC;AAAA,EACjE;AACF;AAGA,eAAsB,WAAW,QAAgB,WAAkD;AACjG,QAAM,KAAK,MAAM;AACjB,MAAI,CAAC,GAAI,QAAO;AAChB,QAAM,OAAO,MAAM,OAAO,IAAI,IAAI,SAAS,QAAQ,YAAY,SAAS,CAAC;AACzE,SAAO,KAAK,OAAO,IAAK,EAAE,IAAI,KAAK,IAAI,GAAI,KAAK,KAAK,EAAc,IAAK;AAC1E;","names":[]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 404-monitor (8.4): adressen waar bezoekers op uitkwamen maar die niet bestaan — een oude link in
|
|
3
|
+
* Google, op drukwerk of in een nieuwsbrief. Met één keuze wordt het een doorverwijzing naar een
|
|
4
|
+
* bestaande pagina (siteadmin; voor een redacteur alleen de lijst).
|
|
5
|
+
*/
|
|
6
|
+
export declare function NietGevondenKaart({ siteId, notFound, magBeheren }: {
|
|
7
|
+
siteId: string;
|
|
8
|
+
notFound?: Record<string, number>;
|
|
9
|
+
magBeheren: boolean;
|
|
10
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useMemo, useState } from "react";
|
|
4
|
+
import { doc, getDoc } from "firebase/firestore";
|
|
5
|
+
import { getDb } from "../../firebase/client.js";
|
|
6
|
+
import { Button, Card } from "../ui/primitives.js";
|
|
7
|
+
import { Select } from "../ui/select.js";
|
|
8
|
+
import { useToast } from "../ui/toast.js";
|
|
9
|
+
import { subscribePages } from "../lib/pagesClient.js";
|
|
10
|
+
import { voegRedirectToe } from "../lib/menuHulp.js";
|
|
11
|
+
import { padLabel } from "./lib.js";
|
|
12
|
+
function NietGevondenKaart({ siteId, notFound, magBeheren }) {
|
|
13
|
+
const { toast } = useToast();
|
|
14
|
+
const [pages, setPages] = useState([]);
|
|
15
|
+
const [omgeleid, setOmgeleid] = useState(/* @__PURE__ */ new Set());
|
|
16
|
+
const [open, setOpen] = useState(null);
|
|
17
|
+
const [doel, setDoel] = useState("/");
|
|
18
|
+
const [bezig, setBezig] = useState(false);
|
|
19
|
+
useEffect(() => magBeheren ? subscribePages(siteId, setPages) : void 0, [siteId, magBeheren]);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const db = getDb();
|
|
22
|
+
if (!db) return;
|
|
23
|
+
void getDoc(doc(db, "sites", siteId, "settings", "public")).then((s) => setOmgeleid(new Set((s.get("redirects") ?? []).map((r) => r.from)))).catch(() => void 0);
|
|
24
|
+
}, [siteId]);
|
|
25
|
+
const rijen = useMemo(
|
|
26
|
+
() => Object.entries(notFound ?? {}).map(([k, n]) => ({ pad: padLabel(k), n })).filter((r) => !omgeleid.has(r.pad)).sort((a, b) => b.n - a.n).slice(0, 10),
|
|
27
|
+
[notFound, omgeleid]
|
|
28
|
+
);
|
|
29
|
+
const opties = useMemo(
|
|
30
|
+
() => pages.filter((p) => !p.trashed).sort((a, b) => a.path < b.path ? -1 : 1).map((p) => ({ value: p.path, label: `${p.title || p.path} (${p.path})` })),
|
|
31
|
+
[pages]
|
|
32
|
+
);
|
|
33
|
+
async function bewaar(from) {
|
|
34
|
+
setBezig(true);
|
|
35
|
+
try {
|
|
36
|
+
await voegRedirectToe(siteId, from, doel);
|
|
37
|
+
setOmgeleid((s) => /* @__PURE__ */ new Set([...s, from]));
|
|
38
|
+
setOpen(null);
|
|
39
|
+
toast({ title: "Doorverwijzing gemaakt", description: `${from} \u2192 ${doel}`, tone: "success" });
|
|
40
|
+
} catch (e) {
|
|
41
|
+
toast({ title: "Doorverwijzen mislukt", description: e.message, tone: "error" });
|
|
42
|
+
} finally {
|
|
43
|
+
setBezig(false);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return /* @__PURE__ */ jsxs(Card, { children: [
|
|
47
|
+
/* @__PURE__ */ jsx("h3", { className: "mb-1 text-sm font-semibold text-slate-800", children: "Niet gevonden" }),
|
|
48
|
+
/* @__PURE__ */ jsxs("p", { className: "mb-3 text-xs text-slate-500", children: [
|
|
49
|
+
"Adressen die bezoekers probeerden maar die niet (meer) bestaan \u2014 vaak een oude link in Google of op drukwerk.",
|
|
50
|
+
magBeheren ? " Stuur ze door naar een bestaande pagina." : ""
|
|
51
|
+
] }),
|
|
52
|
+
rijen.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-slate-500", children: "Niets gemist deze maand." }) : /* @__PURE__ */ jsx("ul", { className: "grid gap-2", children: rijen.map((r) => /* @__PURE__ */ jsxs("li", { className: "rounded-lg border border-slate-200 p-2 text-sm", children: [
|
|
53
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
54
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 truncate font-mono text-xs", title: r.pad, children: r.pad }),
|
|
55
|
+
/* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-2", children: [
|
|
56
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-slate-500", children: [
|
|
57
|
+
r.n,
|
|
58
|
+
"\xD7"
|
|
59
|
+
] }),
|
|
60
|
+
magBeheren && open !== r.pad && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "secondary", onClick: () => {
|
|
61
|
+
setOpen(r.pad);
|
|
62
|
+
setDoel(opties[0]?.value ?? "/");
|
|
63
|
+
}, children: "Doorverwijzen" })
|
|
64
|
+
] })
|
|
65
|
+
] }),
|
|
66
|
+
open === r.pad && /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-2", children: [
|
|
67
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-slate-600", children: "naar" }),
|
|
68
|
+
/* @__PURE__ */ jsx("div", { className: "min-w-48 flex-1", children: /* @__PURE__ */ jsx(Select, { value: doel, onValueChange: setDoel, options: opties, placeholder: "Kies een pagina\u2026" }) }),
|
|
69
|
+
/* @__PURE__ */ jsx(Button, { loading: bezig, size: "sm", disabled: bezig, onClick: () => void bewaar(r.pad), children: "Opslaan" }),
|
|
70
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: () => setOpen(null), children: "Annuleren" })
|
|
71
|
+
] })
|
|
72
|
+
] }, r.pad)) })
|
|
73
|
+
] });
|
|
74
|
+
}
|
|
75
|
+
export {
|
|
76
|
+
NietGevondenKaart
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=NietGevonden.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/stats/NietGevonden.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\nimport { doc, getDoc } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport { Button, Card } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { useToast } from \"../ui/toast\";\nimport { subscribePages, type DraftPage } from \"../lib/pagesClient\";\nimport { voegRedirectToe } from \"../lib/menuHulp\";\nimport { padLabel } from \"./lib\";\n\n/**\n * 404-monitor (8.4): adressen waar bezoekers op uitkwamen maar die niet bestaan — een oude link in\n * Google, op drukwerk of in een nieuwsbrief. Met één keuze wordt het een doorverwijzing naar een\n * bestaande pagina (siteadmin; voor een redacteur alleen de lijst).\n */\nexport function NietGevondenKaart({ siteId, notFound, magBeheren }: { siteId: string; notFound?: Record<string, number>; magBeheren: boolean }) {\n const { toast } = useToast();\n const [pages, setPages] = useState<DraftPage[]>([]);\n const [omgeleid, setOmgeleid] = useState<Set<string>>(new Set());\n const [open, setOpen] = useState<string | null>(null);\n const [doel, setDoel] = useState(\"/\");\n const [bezig, setBezig] = useState(false);\n\n useEffect(() => (magBeheren ? subscribePages(siteId, setPages) : undefined), [siteId, magBeheren]);\n useEffect(() => {\n const db = getDb();\n if (!db) return;\n void getDoc(doc(db, \"sites\", siteId, \"settings\", \"public\"))\n .then((s) => setOmgeleid(new Set(((s.get(\"redirects\") as { from: string }[] | undefined) ?? []).map((r) => r.from))))\n .catch(() => undefined);\n }, [siteId]);\n\n const rijen = useMemo(\n () =>\n Object.entries(notFound ?? {})\n .map(([k, n]) => ({ pad: padLabel(k), n }))\n .filter((r) => !omgeleid.has(r.pad))\n .sort((a, b) => b.n - a.n)\n .slice(0, 10),\n [notFound, omgeleid],\n );\n const opties = useMemo(\n () => pages.filter((p) => !p.trashed).sort((a, b) => (a.path < b.path ? -1 : 1)).map((p) => ({ value: p.path, label: `${p.title || p.path} (${p.path})` })),\n [pages],\n );\n\n async function bewaar(from: string) {\n setBezig(true);\n try {\n await voegRedirectToe(siteId, from, doel);\n setOmgeleid((s) => new Set([...s, from]));\n setOpen(null);\n toast({ title: \"Doorverwijzing gemaakt\", description: `${from} → ${doel}`, tone: \"success\" });\n } catch (e) {\n toast({ title: \"Doorverwijzen mislukt\", description: (e as Error).message, tone: \"error\" });\n } finally {\n setBezig(false);\n }\n }\n\n return (\n <Card>\n <h3 className=\"mb-1 text-sm font-semibold text-slate-800\">Niet gevonden</h3>\n <p className=\"mb-3 text-xs text-slate-500\">\n Adressen die bezoekers probeerden maar die niet (meer) bestaan — vaak een oude link in Google of op\n drukwerk.{magBeheren ? \" Stuur ze door naar een bestaande pagina.\" : \"\"}\n </p>\n {rijen.length === 0 ? (\n <p className=\"text-sm text-slate-500\">Niets gemist deze maand.</p>\n ) : (\n <ul className=\"grid gap-2\">\n {rijen.map((r) => (\n <li key={r.pad} className=\"rounded-lg border border-slate-200 p-2 text-sm\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"min-w-0 truncate font-mono text-xs\" title={r.pad}>{r.pad}</span>\n <span className=\"flex shrink-0 items-center gap-2\">\n <span className=\"text-xs text-slate-500\">{r.n}×</span>\n {magBeheren && open !== r.pad && (\n <Button size=\"sm\" variant=\"secondary\" onClick={() => { setOpen(r.pad); setDoel(opties[0]?.value ?? \"/\"); }}>\n Doorverwijzen\n </Button>\n )}\n </span>\n </div>\n {open === r.pad && (\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <span className=\"text-xs text-slate-600\">naar</span>\n <div className=\"min-w-48 flex-1\">\n <Select value={doel} onValueChange={setDoel} options={opties} placeholder=\"Kies een pagina…\" />\n </div>\n <Button loading={bezig} size=\"sm\" disabled={bezig} onClick={() => void bewaar(r.pad)}>{\"Opslaan\"}</Button>\n <Button size=\"sm\" variant=\"ghost\" onClick={() => setOpen(null)}>Annuleren</Button>\n </div>\n )}\n </li>\n ))}\n </ul>\n )}\n </Card>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -4,6 +4,9 @@ import { useEffect, useMemo, useState } from "react";
|
|
|
4
4
|
import { collection, getDocs } from "firebase/firestore";
|
|
5
5
|
import { getDb } from "../../firebase/client.js";
|
|
6
6
|
import { Card } from "../ui/primitives.js";
|
|
7
|
+
import { NietGevondenKaart } from "./NietGevonden.js";
|
|
8
|
+
import { useAuth } from "../../auth/AuthProvider.js";
|
|
9
|
+
import { canManageSite } from "../../auth/roles.js";
|
|
7
10
|
import { Select } from "../ui/select.js";
|
|
8
11
|
import { AlsTabel, BalkjesLijst, DagStaven, MaandLijn, StatTegel } from "../ui/charts.js";
|
|
9
12
|
import {
|
|
@@ -19,6 +22,7 @@ import {
|
|
|
19
22
|
gebeurtenisLabel
|
|
20
23
|
} from "./lib.js";
|
|
21
24
|
function StatsPanel({ siteId }) {
|
|
25
|
+
const { role } = useAuth();
|
|
22
26
|
const [months, setMonths] = useState([]);
|
|
23
27
|
const [loading, setLoading] = useState(true);
|
|
24
28
|
const [gekozen, setGekozen] = useState(huidigMaandId());
|
|
@@ -154,7 +158,8 @@ function StatsPanel({ siteId }) {
|
|
|
154
158
|
] })
|
|
155
159
|
] })
|
|
156
160
|
] }),
|
|
157
|
-
/* @__PURE__ */ jsx(
|
|
161
|
+
/* @__PURE__ */ jsx(NietGevondenKaart, { siteId, notFound: maand?.notFound, magBeheren: canManageSite(role) }),
|
|
162
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-slate-500", children: "Privacy-vriendelijk geteld (geen cookies, geen persoonsgegevens) \u2014 \xE9\xE9n telling per bezoeker-sessie per pagina. Van de verwijzer wordt alleen de website bewaard, nooit de pagina of zoekterm." })
|
|
158
163
|
] });
|
|
159
164
|
}
|
|
160
165
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/stats/StatsPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\nimport { collection, getDocs } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport { Card } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { AlsTabel, BalkjesLijst, DagStaven, MaandLijn, StatTegel } from \"../ui/charts\";\nimport {\n dagenInMaand,\n dagReeks,\n deltaTovVorige,\n huidigMaandId,\n maandLabel,\n topPaden,\n topVan,\n verwijzerLabel,\n apparaatLabel,\n gebeurtenisLabel,\n type MaandStat,\n} from \"./lib\";\n\n/**\n * Bezoekersstatistieken (7.17) — van drie kale tellers naar een leesbaar verhaal: tegels met\n * context (delta t.o.v. vorige maand, t/m dezelfde dag), een dag-grafiek met hover, de\n * maandtrend als lijn, en de toppagina's als balkjes. Zelfde privacy-vriendelijke bron\n * (`sites/{siteId}/stats`, eigen teller, geen cookies) — er is alleen beter naar gekeken.\n */\nexport function StatsPanel({ siteId }: { siteId: string }) {\n const [months, setMonths] = useState<MaandStat[]>([]);\n const [loading, setLoading] = useState(true);\n const [gekozen, setGekozen] = useState<string>(huidigMaandId());\n\n useEffect(() => {\n const db = getDb();\n if (!db) {\n setLoading(false);\n return;\n }\n let active = true;\n getDocs(collection(db, \"sites\", siteId, \"stats\"))\n .then((snap) => {\n if (!active) return;\n const uit = snap.docs\n .map((d) => ({ id: d.id, ...(d.data() as Omit<MaandStat, \"id\">) }))\n .filter((m) => /^\\d{4}-\\d{2}$/.test(m.id))\n .sort((a, b) => a.id.localeCompare(b.id));\n setMonths(uit);\n // Standaard de nieuwste maand mét data (meestal de huidige).\n if (uit.length && !uit.some((m) => m.id === huidigMaandId())) setGekozen(uit[uit.length - 1].id);\n setLoading(false);\n })\n .catch(() => active && setLoading(false));\n return () => {\n active = false;\n };\n }, [siteId]);\n\n const maand = months.find((m) => m.id === gekozen);\n const vorigeId = useMemo(() => {\n const [j, m] = gekozen.split(\"-\").map(Number);\n const d = new Date(Date.UTC(j, (m ?? 1) - 2, 1));\n return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, \"0\")}`;\n }, [gekozen]);\n const vorige = months.find((m) => m.id === vorigeId);\n\n const isHuidige = gekozen === huidigMaandId();\n const nDagen = dagenInMaand(gekozen);\n const vandaagDag = new Date().getUTCDate();\n const dagen = useMemo(() => dagReeks(maand, nDagen), [maand, nDagen]);\n const totaalMaand = maand?.total ?? 0;\n const totaalOoit = useMemo(() => months.reduce((s, m) => s + (m.total ?? 0), 0), [months]);\n // Gemiddelde over de dagen die er al ZIJN: een lopende maand door 31 delen praat het cijfer omlaag.\n const verstreken = isHuidige ? Math.max(1, vandaagDag) : nDagen;\n const delta = deltaTovVorige(maand, vorige, isHuidige ? vandaagDag : nDagen);\n const paden = useMemo(() => topPaden(maand, 8), [maand]);\n const verwijzers = useMemo(() => topVan(maand?.refs, 8, verwijzerLabel), [maand]);\n const apparaten = useMemo(() => topVan(maand?.devices, 3, apparaatLabel), [maand]);\n const gebeurtenissen = useMemo(() => topVan(maand?.events, 6, gebeurtenisLabel), [maand]);\n const conversie = maand?.events?.formulier || maand?.events?.bestelling\n ? (((maand?.events?.formulier ?? 0) + (maand?.events?.bestelling ?? 0)) / Math.max(1, totaalMaand)) * 100\n : null;\n const trend = useMemo(\n () => months.slice(-12).map((m) => ({ label: maandLabel(m.id), waarde: m.total ?? 0 })),\n [months],\n );\n\n if (loading) return <p className=\"text-sm text-slate-500\">Laden…</p>;\n\n if (months.length === 0) {\n return (\n <Card>\n <p className=\"text-sm text-slate-600\">\n Nog geen bezoekersdata. Zodra de live site bezocht wordt, verschijnen hier de cijfers.\n </p>\n </Card>\n );\n }\n\n return (\n <div className=\"grid gap-4\">\n {/* Eén filterregel boven de inhoud: de maand waar alles hieronder over gaat. */}\n <div className=\"flex items-center justify-between gap-3\">\n <p className=\"text-sm text-slate-500\">\n Bezoek in <span className=\"font-medium text-slate-700\">{maandLabel(gekozen, true)}</span>\n {isHuidige ? \" (loopt nog)\" : \"\"}\n </p>\n <Select\n value={gekozen}\n onValueChange={setGekozen}\n placeholder=\"Maand\"\n className=\"w-44\"\n options={[...months].reverse().map((m) => ({ value: m.id, label: maandLabel(m.id, true) }))}\n />\n </div>\n\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n <StatTegel\n label=\"Weergaven deze maand\"\n waarde={totaalMaand}\n delta={delta}\n deltaLabel={`t.o.v. ${maandLabel(vorigeId)}${isHuidige ? \" (t/m vandaag)\" : \"\"}`}\n />\n {isHuidige ? (\n <StatTegel label=\"Vandaag\" waarde={dagen[vandaagDag - 1] ?? 0} />\n ) : (\n <StatTegel label=\"Drukste dag\" waarde={Math.max(0, ...dagen)} />\n )}\n <StatTegel label=\"Gemiddeld per dag\" waarde={Math.round(totaalMaand / verstreken)} />\n <StatTegel label=\"Totaal gemeten\" waarde={totaalOoit} />\n </div>\n\n <Card>\n <h3 className=\"mb-4 text-sm font-semibold text-slate-800\">Per dag</h3>\n {totaalMaand === 0 ? (\n <p className=\"text-sm text-slate-500\">Nog geen data in deze maand.</p>\n ) : (\n <>\n <DagStaven\n waarden={dagen}\n accentIndex={isHuidige ? vandaagDag - 1 : undefined}\n hoogte={170}\n titel={`Weergaven per dag in ${maandLabel(gekozen, true)}`}\n />\n <AlsTabel\n kop={[\"Dag\", \"Weergaven\"]}\n rijen={dagen.map((v, i) => [`${i + 1} ${maandLabel(gekozen)}`, v] as [string, number]).filter(([, v]) => v > 0)}\n />\n </>\n )}\n </Card>\n\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <Card>\n <h3 className=\"mb-4 text-sm font-semibold text-slate-800\">Maand op maand</h3>\n {trend.length < 2 ? (\n <p className=\"text-sm text-slate-500\">Nog te weinig maanden voor een trend — die groeit hier vanzelf.</p>\n ) : (\n <>\n <MaandLijn punten={trend} hoogte={170} titel=\"Weergaven per maand, laatste twaalf maanden\" />\n <AlsTabel kop={[\"Maand\", \"Weergaven\"]} rijen={trend.map((p) => [p.label, p.waarde] as [string, number])} />\n </>\n )}\n </Card>\n\n <Card>\n <h3 className=\"mb-4 text-sm font-semibold text-slate-800\">Best bezochte pagina's</h3>\n {paden.length === 0 ? (\n <p className=\"text-sm text-slate-500\">Nog geen data in deze maand.</p>\n ) : (\n <BalkjesLijst items={paden} />\n )}\n </Card>\n </div>\n\n {/* 7.24: waar komen bezoekers vandaan, waarop kijken ze, en wat dóén ze — de drie vragen\n die klanten stellen zodra ze een cijfer zien. Verzameld sinds 7.24; oudere maanden\n tonen deze kaarten niet. */}\n {(verwijzers.length > 0 || apparaten.length > 0 || gebeurtenissen.length > 0) && (\n <div className=\"grid gap-4 lg:grid-cols-3\">\n <Card>\n <h3 className=\"mb-4 text-sm font-semibold text-slate-800\">Waar bezoekers vandaan komen</h3>\n {verwijzers.length === 0 ? <p className=\"text-sm text-slate-500\">Nog geen data.</p> : <BalkjesLijst items={verwijzers} />}\n </Card>\n <Card>\n <h3 className=\"mb-4 text-sm font-semibold text-slate-800\">Apparaat</h3>\n {apparaten.length === 0 ? <p className=\"text-sm text-slate-500\">Nog geen data.</p> : <BalkjesLijst items={apparaten} />}\n </Card>\n <Card>\n <h3 className=\"mb-4 text-sm font-semibold text-slate-800\">Wat bezoekers doen</h3>\n {gebeurtenissen.length === 0 ? (\n <p className=\"text-sm text-slate-500\">Nog geen formulieren of bestellingen deze maand.</p>\n ) : (\n <>\n <BalkjesLijst items={gebeurtenissen} />\n {conversie !== null && (\n <p className=\"mt-3 text-xs text-slate-500\">\n {Math.round(conversie * 10) / 10}% van de weergaven leidde tot een formulier of bestelling.\n </p>\n )}\n </>\n )}\n </Card>\n </div>\n )}\n\n <p className=\"text-xs text-slate-400\">\n Privacy-vriendelijk geteld (geen cookies, geen persoonsgegevens) — één telling per\n bezoeker-sessie per pagina. Van de verwijzer wordt alleen de website bewaard, nooit de pagina of zoekterm.\n </p>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/stats/StatsPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\nimport { collection, getDocs } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport { Card } from \"../ui/primitives\";\nimport { NietGevondenKaart } from \"./NietGevonden\";\nimport { useAuth } from \"../../auth/AuthProvider\";\nimport { canManageSite } from \"../../auth/roles\";\nimport { Select } from \"../ui/select\";\nimport { AlsTabel, BalkjesLijst, DagStaven, MaandLijn, StatTegel } from \"../ui/charts\";\nimport {\n dagenInMaand,\n dagReeks,\n deltaTovVorige,\n huidigMaandId,\n maandLabel,\n topPaden,\n topVan,\n verwijzerLabel,\n apparaatLabel,\n gebeurtenisLabel,\n type MaandStat,\n} from \"./lib\";\n\n/**\n * Bezoekersstatistieken (7.17) — van drie kale tellers naar een leesbaar verhaal: tegels met\n * context (delta t.o.v. vorige maand, t/m dezelfde dag), een dag-grafiek met hover, de\n * maandtrend als lijn, en de toppagina's als balkjes. Zelfde privacy-vriendelijke bron\n * (`sites/{siteId}/stats`, eigen teller, geen cookies) — er is alleen beter naar gekeken.\n */\nexport function StatsPanel({ siteId }: { siteId: string }) {\n const { role } = useAuth();\n const [months, setMonths] = useState<MaandStat[]>([]);\n const [loading, setLoading] = useState(true);\n const [gekozen, setGekozen] = useState<string>(huidigMaandId());\n\n useEffect(() => {\n const db = getDb();\n if (!db) {\n setLoading(false);\n return;\n }\n let active = true;\n getDocs(collection(db, \"sites\", siteId, \"stats\"))\n .then((snap) => {\n if (!active) return;\n const uit = snap.docs\n .map((d) => ({ id: d.id, ...(d.data() as Omit<MaandStat, \"id\">) }))\n .filter((m) => /^\\d{4}-\\d{2}$/.test(m.id))\n .sort((a, b) => a.id.localeCompare(b.id));\n setMonths(uit);\n // Standaard de nieuwste maand mét data (meestal de huidige).\n if (uit.length && !uit.some((m) => m.id === huidigMaandId())) setGekozen(uit[uit.length - 1].id);\n setLoading(false);\n })\n .catch(() => active && setLoading(false));\n return () => {\n active = false;\n };\n }, [siteId]);\n\n const maand = months.find((m) => m.id === gekozen);\n const vorigeId = useMemo(() => {\n const [j, m] = gekozen.split(\"-\").map(Number);\n const d = new Date(Date.UTC(j, (m ?? 1) - 2, 1));\n return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, \"0\")}`;\n }, [gekozen]);\n const vorige = months.find((m) => m.id === vorigeId);\n\n const isHuidige = gekozen === huidigMaandId();\n const nDagen = dagenInMaand(gekozen);\n const vandaagDag = new Date().getUTCDate();\n const dagen = useMemo(() => dagReeks(maand, nDagen), [maand, nDagen]);\n const totaalMaand = maand?.total ?? 0;\n const totaalOoit = useMemo(() => months.reduce((s, m) => s + (m.total ?? 0), 0), [months]);\n // Gemiddelde over de dagen die er al ZIJN: een lopende maand door 31 delen praat het cijfer omlaag.\n const verstreken = isHuidige ? Math.max(1, vandaagDag) : nDagen;\n const delta = deltaTovVorige(maand, vorige, isHuidige ? vandaagDag : nDagen);\n const paden = useMemo(() => topPaden(maand, 8), [maand]);\n const verwijzers = useMemo(() => topVan(maand?.refs, 8, verwijzerLabel), [maand]);\n const apparaten = useMemo(() => topVan(maand?.devices, 3, apparaatLabel), [maand]);\n const gebeurtenissen = useMemo(() => topVan(maand?.events, 6, gebeurtenisLabel), [maand]);\n const conversie = maand?.events?.formulier || maand?.events?.bestelling\n ? (((maand?.events?.formulier ?? 0) + (maand?.events?.bestelling ?? 0)) / Math.max(1, totaalMaand)) * 100\n : null;\n const trend = useMemo(\n () => months.slice(-12).map((m) => ({ label: maandLabel(m.id), waarde: m.total ?? 0 })),\n [months],\n );\n\n if (loading) return <p className=\"text-sm text-slate-500\">Laden…</p>;\n\n if (months.length === 0) {\n return (\n <Card>\n <p className=\"text-sm text-slate-600\">\n Nog geen bezoekersdata. Zodra de live site bezocht wordt, verschijnen hier de cijfers.\n </p>\n </Card>\n );\n }\n\n return (\n <div className=\"grid gap-4\">\n {/* Eén filterregel boven de inhoud: de maand waar alles hieronder over gaat. */}\n <div className=\"flex items-center justify-between gap-3\">\n <p className=\"text-sm text-slate-500\">\n Bezoek in <span className=\"font-medium text-slate-700\">{maandLabel(gekozen, true)}</span>\n {isHuidige ? \" (loopt nog)\" : \"\"}\n </p>\n <Select\n value={gekozen}\n onValueChange={setGekozen}\n placeholder=\"Maand\"\n className=\"w-44\"\n options={[...months].reverse().map((m) => ({ value: m.id, label: maandLabel(m.id, true) }))}\n />\n </div>\n\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n <StatTegel\n label=\"Weergaven deze maand\"\n waarde={totaalMaand}\n delta={delta}\n deltaLabel={`t.o.v. ${maandLabel(vorigeId)}${isHuidige ? \" (t/m vandaag)\" : \"\"}`}\n />\n {isHuidige ? (\n <StatTegel label=\"Vandaag\" waarde={dagen[vandaagDag - 1] ?? 0} />\n ) : (\n <StatTegel label=\"Drukste dag\" waarde={Math.max(0, ...dagen)} />\n )}\n <StatTegel label=\"Gemiddeld per dag\" waarde={Math.round(totaalMaand / verstreken)} />\n <StatTegel label=\"Totaal gemeten\" waarde={totaalOoit} />\n </div>\n\n <Card>\n <h3 className=\"mb-4 text-sm font-semibold text-slate-800\">Per dag</h3>\n {totaalMaand === 0 ? (\n <p className=\"text-sm text-slate-500\">Nog geen data in deze maand.</p>\n ) : (\n <>\n <DagStaven\n waarden={dagen}\n accentIndex={isHuidige ? vandaagDag - 1 : undefined}\n hoogte={170}\n titel={`Weergaven per dag in ${maandLabel(gekozen, true)}`}\n />\n <AlsTabel\n kop={[\"Dag\", \"Weergaven\"]}\n rijen={dagen.map((v, i) => [`${i + 1} ${maandLabel(gekozen)}`, v] as [string, number]).filter(([, v]) => v > 0)}\n />\n </>\n )}\n </Card>\n\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <Card>\n <h3 className=\"mb-4 text-sm font-semibold text-slate-800\">Maand op maand</h3>\n {trend.length < 2 ? (\n <p className=\"text-sm text-slate-500\">Nog te weinig maanden voor een trend — die groeit hier vanzelf.</p>\n ) : (\n <>\n <MaandLijn punten={trend} hoogte={170} titel=\"Weergaven per maand, laatste twaalf maanden\" />\n <AlsTabel kop={[\"Maand\", \"Weergaven\"]} rijen={trend.map((p) => [p.label, p.waarde] as [string, number])} />\n </>\n )}\n </Card>\n\n <Card>\n <h3 className=\"mb-4 text-sm font-semibold text-slate-800\">Best bezochte pagina's</h3>\n {paden.length === 0 ? (\n <p className=\"text-sm text-slate-500\">Nog geen data in deze maand.</p>\n ) : (\n <BalkjesLijst items={paden} />\n )}\n </Card>\n </div>\n\n {/* 7.24: waar komen bezoekers vandaan, waarop kijken ze, en wat dóén ze — de drie vragen\n die klanten stellen zodra ze een cijfer zien. Verzameld sinds 7.24; oudere maanden\n tonen deze kaarten niet. */}\n {(verwijzers.length > 0 || apparaten.length > 0 || gebeurtenissen.length > 0) && (\n <div className=\"grid gap-4 lg:grid-cols-3\">\n <Card>\n <h3 className=\"mb-4 text-sm font-semibold text-slate-800\">Waar bezoekers vandaan komen</h3>\n {verwijzers.length === 0 ? <p className=\"text-sm text-slate-500\">Nog geen data.</p> : <BalkjesLijst items={verwijzers} />}\n </Card>\n <Card>\n <h3 className=\"mb-4 text-sm font-semibold text-slate-800\">Apparaat</h3>\n {apparaten.length === 0 ? <p className=\"text-sm text-slate-500\">Nog geen data.</p> : <BalkjesLijst items={apparaten} />}\n </Card>\n <Card>\n <h3 className=\"mb-4 text-sm font-semibold text-slate-800\">Wat bezoekers doen</h3>\n {gebeurtenissen.length === 0 ? (\n <p className=\"text-sm text-slate-500\">Nog geen formulieren of bestellingen deze maand.</p>\n ) : (\n <>\n <BalkjesLijst items={gebeurtenissen} />\n {conversie !== null && (\n <p className=\"mt-3 text-xs text-slate-500\">\n {Math.round(conversie * 10) / 10}% van de weergaven leidde tot een formulier of bestelling.\n </p>\n )}\n </>\n )}\n </Card>\n </div>\n )}\n\n <NietGevondenKaart siteId={siteId} notFound={maand?.notFound} magBeheren={canManageSite(role)} />\n\n <p className=\"text-xs text-slate-500\">\n Privacy-vriendelijk geteld (geen cookies, geen persoonsgegevens) — één telling per\n bezoeker-sessie per pagina. Van de verwijzer wordt alleen de website bewaard, nooit de pagina of zoekterm.\n </p>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -11,6 +11,8 @@ export interface MaandStat {
|
|
|
11
11
|
refs?: Record<string, number>;
|
|
12
12
|
devices?: Record<string, number>;
|
|
13
13
|
events?: Record<string, number>;
|
|
14
|
+
/** 8.4: niet-gevonden paden (404-monitor), sleutel zoals `paths`. */
|
|
15
|
+
notFound?: Record<string, number>;
|
|
14
16
|
}
|
|
15
17
|
/** trackView slaat paden veilig op: "home" voor "/", percent-encoded tekens. Terug naar leesbaar. */
|
|
16
18
|
export declare function padLabel(sleutel: string): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/stats/lib.ts"],"sourcesContent":["/**\n * Gedeelde stats-helpers — gebruikt door het Statistieken-paneel én de Start-widget, zodat\n * paden en maanden op beide plekken hetzelfde heten.\n */\n\nexport interface MaandStat {\n id: string; // \"YYYY-MM\"\n total?: number;\n days?: Record<string, number>;\n paths?: Record<string, number>;\n /** 7.24: verwijzers (host of gebundelde naam zoals \"google\"), apparaten en gebeurtenissen. */\n refs?: Record<string, number>;\n devices?: Record<string, number>;\n events?: Record<string, number>;\n}\n\n/** trackView slaat paden veilig op: \"home\" voor \"/\", percent-encoded tekens. Terug naar leesbaar. */\nexport function padLabel(sleutel: string): string {\n if (sleutel === \"home\") return \"/\";\n let s = sleutel;\n try {\n s = decodeURIComponent(sleutel.replace(/%(?![0-9a-fA-F]{2})/g, \"%25\"));\n } catch {\n /* oude, niet-gecodeerde sleutel: laat staan */\n }\n return `/${s.replace(/_/g, \"/\")}`;\n}\n\n/** Verwijzer-sleutel → leesbaar (\"google\" → \"Google\", \"nieuws%2enl\" → \"nieuws.nl\"). */\nexport function verwijzerLabel(sleutel: string): string {\n const vast: Record<string, string> = {\n direct: \"Direct / onbekend\",\n google: \"Google\",\n bing: \"Bing\",\n duckduckgo: \"DuckDuckGo\",\n facebook: \"Facebook\",\n instagram: \"Instagram\",\n linkedin: \"LinkedIn\",\n x: \"X (Twitter)\",\n };\n if (vast[sleutel]) return vast[sleutel];\n try {\n return decodeURIComponent(sleutel);\n } catch {\n return sleutel;\n }\n}\n\nexport function apparaatLabel(sleutel: string): string {\n return ({ mobiel: \"Mobiel\", tablet: \"Tablet\", desktop: \"Desktop\" } as Record<string, string>)[sleutel] ?? sleutel;\n}\n\nexport function gebeurtenisLabel(sleutel: string): string {\n return (\n ({\n formulier: \"Formulier verstuurd\",\n bestelling: \"Bestelling betaald\",\n uitgaand: \"Uitgaande link\",\n telefoon: \"Telefoonnummer geklikt\",\n mail: \"E-mailadres geklikt\",\n whatsapp: \"WhatsApp geopend\",\n }) as Record<string, string>\n )[sleutel] ?? sleutel;\n}\n\n/** Top-N van een teller-record, gelabeld en gesorteerd. */\nexport function topVan(record: Record<string, number> | undefined, n: number, label: (k: string) => string): { label: string; waarde: number }[] {\n return Object.entries(record ?? {})\n .sort((a, b) => b[1] - a[1])\n .slice(0, n)\n .map(([k, w]) => ({ label: label(k), waarde: w }));\n}\n\n/** \"2026-09\" → \"sep 2026\" (kort, voor as-labels en de maandkiezer). */\nexport function maandLabel(id: string, lang = false): string {\n const [j, m] = id.split(\"-\").map(Number);\n const d = new Date(Date.UTC(j, (m ?? 1) - 1, 1));\n return d.toLocaleDateString(\"nl-NL\", { month: lang ? \"long\" : \"short\", year: \"numeric\", timeZone: \"UTC\" });\n}\n\n/** Het maand-id van nu (UTC — trackView telt in UTC). */\nexport function huidigMaandId(): string {\n const nu = new Date();\n return `${nu.getUTCFullYear()}-${String(nu.getUTCMonth() + 1).padStart(2, \"0\")}`;\n}\n\n/** Aantal dagen in een \"YYYY-MM\"-maand. */\nexport function dagenInMaand(id: string): number {\n const [j, m] = id.split(\"-\").map(Number);\n return new Date(Date.UTC(j, m ?? 1, 0)).getUTCDate();\n}\n\n/** days-record → array per dag (index 0 = dag 1), met nullen voor ontbrekende dagen. */\nexport function dagReeks(stat: MaandStat | undefined, nDagen: number): number[] {\n return Array.from({ length: nDagen }, (_, i) => stat?.days?.[String(i + 1).padStart(2, \"0\")] ?? 0);\n}\n\n/** Top-N paden van een maand, genormaliseerd en gesorteerd. */\nexport function topPaden(stat: MaandStat | undefined, n: number): { label: string; waarde: number }[] {\n return Object.entries(stat?.paths ?? {})\n .sort((a, b) => b[1] - a[1])\n .slice(0, n)\n .map(([p, w]) => ({ label: padLabel(p), waarde: w }));\n}\n\n/**\n * Eerlijke maand-op-maand-delta: vergelijk t/m dezelfde dag. Halverwege de maand het hele\n * vorige maandtotaal ernaast leggen leest als \"−50%\" terwijl het gewoon de 15e is.\n */\nexport function deltaTovVorige(huidig: MaandStat | undefined, vorige: MaandStat | undefined, totDag: number): number | null {\n if (!huidig || !vorige) return null;\n const som = (s: MaandStat) =>\n Object.entries(s.days ?? {}).reduce((acc, [d, n]) => (Number(d) <= totDag ? acc + n : acc), 0);\n const nu = som(huidig);\n const toen = som(vorige);\n if (toen === 0) return null;\n return ((nu - toen) / toen) * 100;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/stats/lib.ts"],"sourcesContent":["/**\n * Gedeelde stats-helpers — gebruikt door het Statistieken-paneel én de Start-widget, zodat\n * paden en maanden op beide plekken hetzelfde heten.\n */\n\nexport interface MaandStat {\n id: string; // \"YYYY-MM\"\n total?: number;\n days?: Record<string, number>;\n paths?: Record<string, number>;\n /** 7.24: verwijzers (host of gebundelde naam zoals \"google\"), apparaten en gebeurtenissen. */\n refs?: Record<string, number>;\n devices?: Record<string, number>;\n events?: Record<string, number>;\n /** 8.4: niet-gevonden paden (404-monitor), sleutel zoals `paths`. */\n notFound?: Record<string, number>;\n}\n\n/** trackView slaat paden veilig op: \"home\" voor \"/\", percent-encoded tekens. Terug naar leesbaar. */\nexport function padLabel(sleutel: string): string {\n if (sleutel === \"home\") return \"/\";\n let s = sleutel;\n try {\n s = decodeURIComponent(sleutel.replace(/%(?![0-9a-fA-F]{2})/g, \"%25\"));\n } catch {\n /* oude, niet-gecodeerde sleutel: laat staan */\n }\n return `/${s.replace(/_/g, \"/\")}`;\n}\n\n/** Verwijzer-sleutel → leesbaar (\"google\" → \"Google\", \"nieuws%2enl\" → \"nieuws.nl\"). */\nexport function verwijzerLabel(sleutel: string): string {\n const vast: Record<string, string> = {\n direct: \"Direct / onbekend\",\n google: \"Google\",\n bing: \"Bing\",\n duckduckgo: \"DuckDuckGo\",\n facebook: \"Facebook\",\n instagram: \"Instagram\",\n linkedin: \"LinkedIn\",\n x: \"X (Twitter)\",\n };\n if (vast[sleutel]) return vast[sleutel];\n try {\n return decodeURIComponent(sleutel);\n } catch {\n return sleutel;\n }\n}\n\nexport function apparaatLabel(sleutel: string): string {\n return ({ mobiel: \"Mobiel\", tablet: \"Tablet\", desktop: \"Desktop\" } as Record<string, string>)[sleutel] ?? sleutel;\n}\n\nexport function gebeurtenisLabel(sleutel: string): string {\n return (\n ({\n formulier: \"Formulier verstuurd\",\n bestelling: \"Bestelling betaald\",\n uitgaand: \"Uitgaande link\",\n telefoon: \"Telefoonnummer geklikt\",\n mail: \"E-mailadres geklikt\",\n whatsapp: \"WhatsApp geopend\",\n }) as Record<string, string>\n )[sleutel] ?? sleutel;\n}\n\n/** Top-N van een teller-record, gelabeld en gesorteerd. */\nexport function topVan(record: Record<string, number> | undefined, n: number, label: (k: string) => string): { label: string; waarde: number }[] {\n return Object.entries(record ?? {})\n .sort((a, b) => b[1] - a[1])\n .slice(0, n)\n .map(([k, w]) => ({ label: label(k), waarde: w }));\n}\n\n/** \"2026-09\" → \"sep 2026\" (kort, voor as-labels en de maandkiezer). */\nexport function maandLabel(id: string, lang = false): string {\n const [j, m] = id.split(\"-\").map(Number);\n const d = new Date(Date.UTC(j, (m ?? 1) - 1, 1));\n return d.toLocaleDateString(\"nl-NL\", { month: lang ? \"long\" : \"short\", year: \"numeric\", timeZone: \"UTC\" });\n}\n\n/** Het maand-id van nu (UTC — trackView telt in UTC). */\nexport function huidigMaandId(): string {\n const nu = new Date();\n return `${nu.getUTCFullYear()}-${String(nu.getUTCMonth() + 1).padStart(2, \"0\")}`;\n}\n\n/** Aantal dagen in een \"YYYY-MM\"-maand. */\nexport function dagenInMaand(id: string): number {\n const [j, m] = id.split(\"-\").map(Number);\n return new Date(Date.UTC(j, m ?? 1, 0)).getUTCDate();\n}\n\n/** days-record → array per dag (index 0 = dag 1), met nullen voor ontbrekende dagen. */\nexport function dagReeks(stat: MaandStat | undefined, nDagen: number): number[] {\n return Array.from({ length: nDagen }, (_, i) => stat?.days?.[String(i + 1).padStart(2, \"0\")] ?? 0);\n}\n\n/** Top-N paden van een maand, genormaliseerd en gesorteerd. */\nexport function topPaden(stat: MaandStat | undefined, n: number): { label: string; waarde: number }[] {\n return Object.entries(stat?.paths ?? {})\n .sort((a, b) => b[1] - a[1])\n .slice(0, n)\n .map(([p, w]) => ({ label: padLabel(p), waarde: w }));\n}\n\n/**\n * Eerlijke maand-op-maand-delta: vergelijk t/m dezelfde dag. Halverwege de maand het hele\n * vorige maandtotaal ernaast leggen leest als \"−50%\" terwijl het gewoon de 15e is.\n */\nexport function deltaTovVorige(huidig: MaandStat | undefined, vorige: MaandStat | undefined, totDag: number): number | null {\n if (!huidig || !vorige) return null;\n const som = (s: MaandStat) =>\n Object.entries(s.days ?? {}).reduce((acc, [d, n]) => (Number(d) <= totDag ? acc + n : acc), 0);\n const nu = som(huidig);\n const toen = som(vorige);\n if (toen === 0) return null;\n return ((nu - toen) / toen) * 100;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { createContext, useCallback, useContext, useRef, useState } from "react";
|
|
4
4
|
import { AlertDialog } from "@astryxdesign/core/AlertDialog";
|
|
5
|
+
import { Dialog, DialogContent, DialogFooter } from "./dialog.js";
|
|
6
|
+
import { Button } from "./primitives.js";
|
|
5
7
|
const ConfirmContext = createContext(null);
|
|
6
8
|
function ConfirmHost({ children }) {
|
|
7
9
|
const [open, setOpen] = useState(false);
|
|
@@ -21,7 +23,14 @@ function ConfirmHost({ children }) {
|
|
|
21
23
|
}, []);
|
|
22
24
|
return /* @__PURE__ */ jsxs(ConfirmContext.Provider, { value: confirm, children: [
|
|
23
25
|
children,
|
|
24
|
-
opts && /* @__PURE__ */ jsx(
|
|
26
|
+
opts && typeof opts.description !== "string" && opts.description != null && /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (o) => !o && close(false), children: /* @__PURE__ */ jsxs(DialogContent, { title: opts.title, width: "min(92vw, 34rem)", children: [
|
|
27
|
+
/* @__PURE__ */ jsx("div", { role: "alert", className: "max-h-[60vh] overflow-y-auto text-sm text-slate-700", children: opts.description }),
|
|
28
|
+
/* @__PURE__ */ jsxs(DialogFooter, { children: [
|
|
29
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: () => close(false), children: opts.cancelLabel ?? "Annuleren" }),
|
|
30
|
+
/* @__PURE__ */ jsx(Button, { variant: opts.tone === "danger" ? "danger" : "primary", onClick: () => close(true), children: opts.confirmLabel ?? "Bevestigen" })
|
|
31
|
+
] })
|
|
32
|
+
] }) }),
|
|
33
|
+
opts && (typeof opts.description === "string" || opts.description == null) && /* @__PURE__ */ jsx(
|
|
25
34
|
AlertDialog,
|
|
26
35
|
{
|
|
27
36
|
isOpen: open,
|