@dashboard-designer/ui 0.1.0 → 0.1.1

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/index.js CHANGED
@@ -1,20 +1,21 @@
1
1
  // src/designer/DashboardDesigner.tsx
2
- import React11, { useEffect as useEffect11, useMemo as useMemo6, useState as useState11 } from "react";
2
+ import React13, { useEffect as useEffect13, useMemo as useMemo6, useState as useState12 } from "react";
3
3
  import GridLayout from "react-grid-layout";
4
- import { useTranslation as useTranslation13 } from "react-i18next";
4
+ import { useTranslation as useTranslation15 } from "react-i18next";
5
5
  import {
6
6
  ArrowUpLeft,
7
7
  FileDown,
8
8
  History as History2,
9
+ Mail as Mail2,
9
10
  Pencil,
10
- Plus as Plus5,
11
+ Plus as Plus6,
11
12
  Printer,
12
13
  Redo2,
13
14
  RotateCcw as RotateCcw3,
14
15
  Save as Save2,
15
16
  Settings2,
16
17
  Share2,
17
- Trash2 as Trash24,
18
+ Trash2 as Trash25,
18
19
  Undo2
19
20
  } from "lucide-react";
20
21
  import {
@@ -79,6 +80,11 @@ var DashboardApiClient = class {
79
80
  body: JSON.stringify(connection)
80
81
  });
81
82
  }
83
+ deleteConnection(id) {
84
+ return this.req(`/api/connections/${id}`, {
85
+ method: "DELETE"
86
+ });
87
+ }
82
88
  testConnection(id) {
83
89
  return this.req(`/api/connections/${id}/test`, {
84
90
  method: "POST",
@@ -150,6 +156,15 @@ var DashboardApiClient = class {
150
156
  body: JSON.stringify(input)
151
157
  });
152
158
  }
159
+ emailStatus() {
160
+ return this.req("/api/email/status");
161
+ }
162
+ sendDashboardEmail(input) {
163
+ return this.req("/api/email/send", {
164
+ method: "POST",
165
+ body: JSON.stringify(input)
166
+ });
167
+ }
153
168
  };
154
169
 
155
170
  // src/i18n.ts
@@ -360,13 +375,55 @@ var resources = {
360
375
  exportOptions: "Export & print",
361
376
  allowPrint: "Allow print",
362
377
  allowExportPdf: "Allow export to PDF",
378
+ allowEmail: "Allow send email",
379
+ emailAttachmentOptions: "Email attachments",
380
+ emailAttachPdf: "Attach dashboard PDF",
381
+ emailAllowExtraAttachments: "Allow additional files",
382
+ emailMaxExtraAttachments: "Max additional files",
363
383
  print: "Print",
364
384
  printDashboard: "Print dashboard",
365
385
  exportPdf: "Export PDF",
366
386
  exportPdfHint: "Open the print dialog and choose Save as PDF",
387
+ emailAction: "Email",
388
+ sendEmail: "Send email",
389
+ composeEmail: "Compose email",
390
+ emailTo: "To",
391
+ emailCc: "Cc",
392
+ emailSubject: "Subject",
393
+ emailBody: "Message",
394
+ emailAttachment: "Attachment",
395
+ emailAttachments: "Attachments",
396
+ emailAddAttachment: "Add file",
397
+ emailAttachmentPdfNote: "Generated on send",
398
+ emailTooManyAttachments: "You can add up to {{count}} extra files",
399
+ emailBodyPlaceholder: "Write your message\u2026",
400
+ emailFormatToolbar: "Formatting",
401
+ emailBold: "Bold",
402
+ emailItalic: "Italic",
403
+ emailUnderline: "Underline",
404
+ emailBulletList: "Bullet list",
405
+ emailNumberedList: "Numbered list",
406
+ emailInsertLink: "Insert link",
407
+ emailClearFormat: "Clear formatting",
408
+ emailLinkPrompt: "Link URL",
409
+ emailSending: "Sending\u2026",
410
+ emailSent: "Email sent",
411
+ emailNotConfigured: "Email is not configured on the server. Set DD_SMTP_HOST and DD_SMTP_FROM.",
412
+ emailComposeHint: "Compose a formatted message. A PDF of the dashboard is attached automatically; you can add more files.",
413
+ emailDefaultSubject: "Dashboard: {{title}}",
414
+ emailDefaultBody: "Please find the dashboard \u201C{{title}}\u201D attached as a PDF.",
367
415
  helpExportOptions: "Control which export actions appear in the dashboard toolbar.",
368
416
  helpAllowPrint: "Shows a Print button that opens the browser print dialog for this dashboard.",
369
417
  helpAllowExportPdf: "Shows Export PDF. Uses the print dialog so charts render correctly \u2014 choose Save as PDF as the destination.",
418
+ helpAllowEmail: "Shows Send email in the toolbar. Requires SMTP settings on the API (DD_SMTP_*).",
419
+ helpEmailAttachPdf: "When enabled, Send email generates and attaches a PDF snapshot of the dashboard.",
420
+ helpEmailAllowExtraAttachments: "When enabled, users can add their own files in the compose dialog.",
421
+ helpEmailMaxExtraAttachments: "Maximum number of user-added files (0\u201310).",
422
+ helpEmailTo: "Recipient addresses, separated by commas.",
423
+ helpEmailCc: "Optional Cc recipients, separated by commas.",
424
+ helpEmailBody: "Format with the toolbar: bold, italic, lists, and links. Sent as HTML email.",
425
+ helpEmailAttachment: "A PDF snapshot of the visible dashboard canvas is generated when you click Send.",
426
+ helpEmailExtraAttachments: "Optional extra files (up to 5, 10MB each). Images, PDFs, and documents are supported.",
370
427
  themeColors: "Color theme",
371
428
  themePreset: "Theme",
372
429
  themePresetDefault: "Default (forest)",
@@ -617,13 +674,55 @@ var resources = {
617
674
  exportOptions: "Exportar e imprimir",
618
675
  allowPrint: "Permitir imprimir",
619
676
  allowExportPdf: "Permitir exportar a PDF",
677
+ allowEmail: "Permitir enviar correo",
678
+ emailAttachmentOptions: "Adjuntos del correo",
679
+ emailAttachPdf: "Adjuntar PDF del panel",
680
+ emailAllowExtraAttachments: "Permitir archivos adicionales",
681
+ emailMaxExtraAttachments: "M\xE1x. archivos adicionales",
620
682
  print: "Imprimir",
621
683
  printDashboard: "Imprimir panel",
622
684
  exportPdf: "Exportar PDF",
623
685
  exportPdfHint: "Abre el di\xE1logo de impresi\xF3n y elija Guardar como PDF",
686
+ emailAction: "Correo",
687
+ sendEmail: "Enviar correo",
688
+ composeEmail: "Redactar correo",
689
+ emailTo: "Para",
690
+ emailCc: "Cc",
691
+ emailSubject: "Asunto",
692
+ emailBody: "Mensaje",
693
+ emailAttachment: "Adjunto",
694
+ emailAttachments: "Adjuntos",
695
+ emailAddAttachment: "A\xF1adir archivo",
696
+ emailAttachmentPdfNote: "Se genera al enviar",
697
+ emailTooManyAttachments: "Puede a\xF1adir hasta {{count}} archivos extra",
698
+ emailBodyPlaceholder: "Escriba su mensaje\u2026",
699
+ emailFormatToolbar: "Formato",
700
+ emailBold: "Negrita",
701
+ emailItalic: "Cursiva",
702
+ emailUnderline: "Subrayado",
703
+ emailBulletList: "Lista con vi\xF1etas",
704
+ emailNumberedList: "Lista numerada",
705
+ emailInsertLink: "Insertar enlace",
706
+ emailClearFormat: "Quitar formato",
707
+ emailLinkPrompt: "URL del enlace",
708
+ emailSending: "Enviando\u2026",
709
+ emailSent: "Correo enviado",
710
+ emailNotConfigured: "El correo no est\xE1 configurado en el servidor. Defina DD_SMTP_HOST y DD_SMTP_FROM.",
711
+ emailComposeHint: "Redacte un mensaje con formato. Se adjunta autom\xE1ticamente un PDF del panel; puede a\xF1adir m\xE1s archivos.",
712
+ emailDefaultSubject: "Panel: {{title}}",
713
+ emailDefaultBody: "Adjunto encontrar\xE1 el panel \u201C{{title}}\u201D en PDF.",
624
714
  helpExportOptions: "Controla qu\xE9 acciones de exportaci\xF3n aparecen en la barra del panel.",
625
715
  helpAllowPrint: "Muestra Imprimir y abre el di\xE1logo de impresi\xF3n del navegador.",
626
716
  helpAllowExportPdf: "Muestra Exportar PDF. Usa el di\xE1logo de impresi\xF3n para que los gr\xE1ficos se vean bien \u2014 elija Guardar como PDF.",
717
+ helpAllowEmail: "Muestra Enviar correo en la barra. Requiere SMTP en la API (DD_SMTP_*).",
718
+ helpEmailAttachPdf: "Si est\xE1 activo, Enviar correo genera y adjunta una instant\xE1nea PDF del panel.",
719
+ helpEmailAllowExtraAttachments: "Si est\xE1 activo, los usuarios pueden a\xF1adir archivos en el di\xE1logo de composici\xF3n.",
720
+ helpEmailMaxExtraAttachments: "N\xFAmero m\xE1ximo de archivos a\xF1adidos por el usuario (0\u201310).",
721
+ helpEmailTo: "Direcciones de destinatario, separadas por comas.",
722
+ helpEmailCc: "Destinatarios en copia (opcional), separados por comas.",
723
+ helpEmailBody: "Use la barra para negrita, cursiva, listas y enlaces. Se env\xEDa como HTML.",
724
+ helpEmailAttachment: "Al pulsar Enviar se genera una instant\xE1nea PDF del lienzo visible del panel.",
725
+ helpEmailExtraAttachments: "Archivos opcionales (hasta 5, 10 MB cada uno). Se admiten im\xE1genes, PDF y documentos.",
627
726
  themeColors: "Tema de color",
628
727
  themePreset: "Tema",
629
728
  themePresetDefault: "Predeterminado (bosque)",
@@ -3533,17 +3632,581 @@ function SharePanel({ open, onClose, dashboardId, api }) {
3533
3632
  ) });
3534
3633
  }
3535
3634
 
3536
- // src/designer/DashboardSettingsPanel.tsx
3537
- import { useEffect as useEffect9, useMemo as useMemo5, useState as useState9 } from "react";
3538
- import { Plus as Plus4, RotateCcw as RotateCcw2, Trash2 as Trash23, X as X7 } from "lucide-react";
3635
+ // src/designer/EmailComposePanel.tsx
3636
+ import { useEffect as useEffect10, useId as useId5, useRef as useRef7, useState as useState9 } from "react";
3637
+ import { Mail, Paperclip, Plus as Plus4, Trash2 as Trash23, X as X7 } from "lucide-react";
3638
+ import { useTranslation as useTranslation13 } from "react-i18next";
3639
+ import { resolveEmailOptions } from "@dashboard-designer/core";
3640
+
3641
+ // src/components/RichTextEditor.tsx
3642
+ import { useEffect as useEffect9, useRef as useRef6 } from "react";
3643
+ import {
3644
+ Bold,
3645
+ Italic,
3646
+ Link2 as Link22,
3647
+ List,
3648
+ ListOrdered,
3649
+ RemoveFormatting,
3650
+ Underline
3651
+ } from "lucide-react";
3539
3652
  import { useTranslation as useTranslation12 } from "react-i18next";
3653
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
3654
+ function escapeHtml(text) {
3655
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
3656
+ }
3657
+ function plainTextToEmailHtml(text) {
3658
+ const trimmed = text.trim();
3659
+ if (!trimmed) return "<p><br></p>";
3660
+ if (/<[a-z][\s\S]*>/i.test(trimmed)) return trimmed;
3661
+ return trimmed.split(/\n{2,}/).map((block) => `<p>${escapeHtml(block).replace(/\n/g, "<br>")}</p>`).join("");
3662
+ }
3663
+ function htmlToPlainText(html) {
3664
+ if (typeof document === "undefined") {
3665
+ return html.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
3666
+ }
3667
+ const el = document.createElement("div");
3668
+ el.innerHTML = html;
3669
+ return (el.innerText || el.textContent || "").replace(/\u00a0/g, " ").trim();
3670
+ }
3671
+ function runCommand(command, value) {
3672
+ document.execCommand(command, false, value);
3673
+ }
3674
+ function RichTextEditor({
3675
+ id,
3676
+ valueHtml,
3677
+ onChange,
3678
+ disabled,
3679
+ placeholder
3680
+ }) {
3681
+ const { t } = useTranslation12();
3682
+ const ref = useRef6(null);
3683
+ const lastEmitted = useRef6(valueHtml);
3684
+ useEffect9(() => {
3685
+ const el = ref.current;
3686
+ if (!el) return;
3687
+ if (valueHtml !== lastEmitted.current && el.innerHTML !== valueHtml) {
3688
+ el.innerHTML = valueHtml || "<p><br></p>";
3689
+ lastEmitted.current = valueHtml;
3690
+ }
3691
+ }, [valueHtml]);
3692
+ const emit = () => {
3693
+ const el = ref.current;
3694
+ if (!el) return;
3695
+ const html = el.innerHTML;
3696
+ lastEmitted.current = html;
3697
+ onChange(html);
3698
+ };
3699
+ const format = (command, value) => {
3700
+ if (disabled) return;
3701
+ ref.current?.focus();
3702
+ runCommand(command, value);
3703
+ emit();
3704
+ };
3705
+ const addLink = () => {
3706
+ if (disabled) return;
3707
+ const url = window.prompt(t("emailLinkPrompt"), "https://");
3708
+ if (!url) return;
3709
+ format("createLink", url);
3710
+ };
3711
+ return /* @__PURE__ */ jsxs12("div", { className: `dd-rte${disabled ? " is-disabled" : ""}`, children: [
3712
+ /* @__PURE__ */ jsxs12("div", { className: "dd-rte-toolbar", role: "toolbar", "aria-label": t("emailFormatToolbar"), children: [
3713
+ /* @__PURE__ */ jsx12(
3714
+ "button",
3715
+ {
3716
+ type: "button",
3717
+ className: "dd-btn dd-btn-ghost dd-btn-icon",
3718
+ title: t("emailBold"),
3719
+ "aria-label": t("emailBold"),
3720
+ disabled,
3721
+ onMouseDown: (e) => e.preventDefault(),
3722
+ onClick: () => format("bold"),
3723
+ children: /* @__PURE__ */ jsx12(Bold, { size: ICON_SIZE })
3724
+ }
3725
+ ),
3726
+ /* @__PURE__ */ jsx12(
3727
+ "button",
3728
+ {
3729
+ type: "button",
3730
+ className: "dd-btn dd-btn-ghost dd-btn-icon",
3731
+ title: t("emailItalic"),
3732
+ "aria-label": t("emailItalic"),
3733
+ disabled,
3734
+ onMouseDown: (e) => e.preventDefault(),
3735
+ onClick: () => format("italic"),
3736
+ children: /* @__PURE__ */ jsx12(Italic, { size: ICON_SIZE })
3737
+ }
3738
+ ),
3739
+ /* @__PURE__ */ jsx12(
3740
+ "button",
3741
+ {
3742
+ type: "button",
3743
+ className: "dd-btn dd-btn-ghost dd-btn-icon",
3744
+ title: t("emailUnderline"),
3745
+ "aria-label": t("emailUnderline"),
3746
+ disabled,
3747
+ onMouseDown: (e) => e.preventDefault(),
3748
+ onClick: () => format("underline"),
3749
+ children: /* @__PURE__ */ jsx12(Underline, { size: ICON_SIZE })
3750
+ }
3751
+ ),
3752
+ /* @__PURE__ */ jsx12("span", { className: "dd-rte-sep", "aria-hidden": true }),
3753
+ /* @__PURE__ */ jsx12(
3754
+ "button",
3755
+ {
3756
+ type: "button",
3757
+ className: "dd-btn dd-btn-ghost dd-btn-icon",
3758
+ title: t("emailBulletList"),
3759
+ "aria-label": t("emailBulletList"),
3760
+ disabled,
3761
+ onMouseDown: (e) => e.preventDefault(),
3762
+ onClick: () => format("insertUnorderedList"),
3763
+ children: /* @__PURE__ */ jsx12(List, { size: ICON_SIZE })
3764
+ }
3765
+ ),
3766
+ /* @__PURE__ */ jsx12(
3767
+ "button",
3768
+ {
3769
+ type: "button",
3770
+ className: "dd-btn dd-btn-ghost dd-btn-icon",
3771
+ title: t("emailNumberedList"),
3772
+ "aria-label": t("emailNumberedList"),
3773
+ disabled,
3774
+ onMouseDown: (e) => e.preventDefault(),
3775
+ onClick: () => format("insertOrderedList"),
3776
+ children: /* @__PURE__ */ jsx12(ListOrdered, { size: ICON_SIZE })
3777
+ }
3778
+ ),
3779
+ /* @__PURE__ */ jsx12("span", { className: "dd-rte-sep", "aria-hidden": true }),
3780
+ /* @__PURE__ */ jsx12(
3781
+ "button",
3782
+ {
3783
+ type: "button",
3784
+ className: "dd-btn dd-btn-ghost dd-btn-icon",
3785
+ title: t("emailInsertLink"),
3786
+ "aria-label": t("emailInsertLink"),
3787
+ disabled,
3788
+ onMouseDown: (e) => e.preventDefault(),
3789
+ onClick: addLink,
3790
+ children: /* @__PURE__ */ jsx12(Link22, { size: ICON_SIZE })
3791
+ }
3792
+ ),
3793
+ /* @__PURE__ */ jsx12(
3794
+ "button",
3795
+ {
3796
+ type: "button",
3797
+ className: "dd-btn dd-btn-ghost dd-btn-icon",
3798
+ title: t("emailClearFormat"),
3799
+ "aria-label": t("emailClearFormat"),
3800
+ disabled,
3801
+ onMouseDown: (e) => e.preventDefault(),
3802
+ onClick: () => format("removeFormat"),
3803
+ children: /* @__PURE__ */ jsx12(RemoveFormatting, { size: ICON_SIZE })
3804
+ }
3805
+ )
3806
+ ] }),
3807
+ /* @__PURE__ */ jsx12(
3808
+ "div",
3809
+ {
3810
+ id,
3811
+ ref,
3812
+ className: "dd-rte-editor dd-input",
3813
+ contentEditable: !disabled,
3814
+ role: "textbox",
3815
+ "aria-multiline": "true",
3816
+ "data-placeholder": placeholder,
3817
+ suppressContentEditableWarning: true,
3818
+ onInput: emit,
3819
+ onBlur: emit
3820
+ }
3821
+ )
3822
+ ] });
3823
+ }
3824
+
3825
+ // src/export/captureDashboardPdf.ts
3826
+ import html2canvas from "html2canvas";
3827
+ import { jsPDF } from "jspdf";
3828
+ function slugifyTitle2(title) {
3829
+ const s = title.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3830
+ return s || "dashboard";
3831
+ }
3832
+ function blobToBase64(blob) {
3833
+ return new Promise((resolve, reject) => {
3834
+ const reader = new FileReader();
3835
+ reader.onload = () => {
3836
+ const result = String(reader.result || "");
3837
+ const comma = result.indexOf(",");
3838
+ resolve(comma >= 0 ? result.slice(comma + 1) : result);
3839
+ };
3840
+ reader.onerror = () => reject(reader.error || new Error("Failed to read PDF"));
3841
+ reader.readAsDataURL(blob);
3842
+ });
3843
+ }
3844
+ async function captureDashboardPdfAttachment(canvasEl, title) {
3845
+ if (!canvasEl) {
3846
+ throw new Error("Dashboard canvas not found. Close this dialog and try again.");
3847
+ }
3848
+ const shot = await html2canvas(canvasEl, {
3849
+ scale: Math.min(2, window.devicePixelRatio || 1.5),
3850
+ useCORS: true,
3851
+ allowTaint: true,
3852
+ logging: false,
3853
+ backgroundColor: "#ffffff",
3854
+ scrollX: 0,
3855
+ scrollY: -window.scrollY
3856
+ });
3857
+ const imgWidthPx = shot.width;
3858
+ const imgHeightPx = shot.height;
3859
+ if (imgWidthPx < 8 || imgHeightPx < 8) {
3860
+ throw new Error("Could not capture dashboard for PDF");
3861
+ }
3862
+ const orientation = imgWidthPx >= imgHeightPx ? "landscape" : "portrait";
3863
+ const pdf = new jsPDF({ orientation, unit: "pt", format: "a4", compress: true });
3864
+ const pageWidth = pdf.internal.pageSize.getWidth();
3865
+ const pageHeight = pdf.internal.pageSize.getHeight();
3866
+ const margin = 24;
3867
+ const usableWidth = pageWidth - margin * 2;
3868
+ const usableHeight = pageHeight - margin * 2;
3869
+ const scale = usableWidth / imgWidthPx;
3870
+ const pageSliceHeightPx = usableHeight / scale;
3871
+ let yPx = 0;
3872
+ let pageIndex = 0;
3873
+ while (yPx < imgHeightPx - 0.5) {
3874
+ if (pageIndex > 0) pdf.addPage();
3875
+ const sliceHeightPx = Math.min(pageSliceHeightPx, imgHeightPx - yPx);
3876
+ const sliceCanvas = document.createElement("canvas");
3877
+ sliceCanvas.width = imgWidthPx;
3878
+ sliceCanvas.height = Math.max(1, Math.ceil(sliceHeightPx));
3879
+ const ctx = sliceCanvas.getContext("2d");
3880
+ if (!ctx) throw new Error("Could not create PDF page");
3881
+ ctx.fillStyle = "#ffffff";
3882
+ ctx.fillRect(0, 0, sliceCanvas.width, sliceCanvas.height);
3883
+ ctx.drawImage(
3884
+ shot,
3885
+ 0,
3886
+ yPx,
3887
+ imgWidthPx,
3888
+ sliceHeightPx,
3889
+ 0,
3890
+ 0,
3891
+ imgWidthPx,
3892
+ sliceHeightPx
3893
+ );
3894
+ const sliceData = sliceCanvas.toDataURL("image/jpeg", 0.92);
3895
+ const sliceRenderHeight = sliceHeightPx * scale;
3896
+ pdf.addImage(sliceData, "JPEG", margin, margin, usableWidth, sliceRenderHeight);
3897
+ yPx += sliceHeightPx;
3898
+ pageIndex += 1;
3899
+ if (pageIndex > 40) break;
3900
+ }
3901
+ const blob = pdf.output("blob");
3902
+ const contentBase64 = await blobToBase64(blob);
3903
+ return {
3904
+ filename: `${slugifyTitle2(title)}.pdf`,
3905
+ contentType: "application/pdf",
3906
+ contentBase64,
3907
+ byteLength: blob.size
3908
+ };
3909
+ }
3910
+
3911
+ // src/designer/EmailComposePanel.tsx
3912
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
3913
+ var MAX_EXTRA_BYTES = 10 * 1024 * 1024;
3914
+ function slugify(title) {
3915
+ const s = title.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3916
+ return s || "dashboard";
3917
+ }
3918
+ function readFileAsAttachment(file) {
3919
+ return new Promise((resolve, reject) => {
3920
+ if (file.size > MAX_EXTRA_BYTES) {
3921
+ reject(new Error(`File \u201C${file.name}\u201D exceeds 10MB`));
3922
+ return;
3923
+ }
3924
+ const reader = new FileReader();
3925
+ reader.onload = () => {
3926
+ const result = String(reader.result || "");
3927
+ const comma = result.indexOf(",");
3928
+ const contentBase64 = comma >= 0 ? result.slice(comma + 1) : result;
3929
+ resolve({
3930
+ id: crypto.randomUUID(),
3931
+ filename: file.name,
3932
+ contentType: file.type || "application/octet-stream",
3933
+ contentBase64
3934
+ });
3935
+ };
3936
+ reader.onerror = () => reject(reader.error || new Error(`Failed to read ${file.name}`));
3937
+ reader.readAsDataURL(file);
3938
+ });
3939
+ }
3940
+ function EmailComposePanel({
3941
+ open,
3942
+ onClose,
3943
+ dashboard,
3944
+ api,
3945
+ canvasElementId = "dd-canvas"
3946
+ }) {
3947
+ const { t } = useTranslation13();
3948
+ const fileInputId = useId5();
3949
+ const fileInputRef = useRef7(null);
3950
+ const emailOpts = resolveEmailOptions(dashboard.exportOptions);
3951
+ const maxExtra = emailOpts.maxExtraAttachments;
3952
+ const [to, setTo] = useState9("");
3953
+ const [cc, setCc] = useState9("");
3954
+ const [subject, setSubject] = useState9("");
3955
+ const [bodyHtml, setBodyHtml] = useState9("<p><br></p>");
3956
+ const [extraAttachments, setExtraAttachments] = useState9([]);
3957
+ const [configured, setConfigured] = useState9(null);
3958
+ const [sending, setSending] = useState9(false);
3959
+ const [error, setError] = useState9(null);
3960
+ const [success, setSuccess] = useState9(null);
3961
+ const dashboardPdfName = `${slugify(dashboard.title)}.pdf`;
3962
+ const showAttachments = emailOpts.attachPdf || emailOpts.allowExtraAttachments && maxExtra > 0;
3963
+ useEffect10(() => {
3964
+ if (!open) return;
3965
+ setError(null);
3966
+ setSuccess(null);
3967
+ setTo("");
3968
+ setCc("");
3969
+ setSubject(t("emailDefaultSubject", { title: dashboard.title }));
3970
+ setBodyHtml(plainTextToEmailHtml(t("emailDefaultBody", { title: dashboard.title })));
3971
+ setExtraAttachments([]);
3972
+ setConfigured(null);
3973
+ api.emailStatus().then((r) => setConfigured(r.configured)).catch(() => setConfigured(false));
3974
+ }, [open, dashboard.id, dashboard.title, api, t]);
3975
+ if (!open) return null;
3976
+ const addFiles = async (files) => {
3977
+ if (!files?.length || !emailOpts.allowExtraAttachments) return;
3978
+ setError(null);
3979
+ try {
3980
+ const remaining = maxExtra - extraAttachments.length;
3981
+ if (remaining <= 0) {
3982
+ setError(t("emailTooManyAttachments", { count: maxExtra }));
3983
+ return;
3984
+ }
3985
+ const picked = Array.from(files).slice(0, remaining);
3986
+ const loaded = await Promise.all(picked.map(readFileAsAttachment));
3987
+ setExtraAttachments((prev) => [...prev, ...loaded]);
3988
+ } catch (err) {
3989
+ setError(err.message);
3990
+ } finally {
3991
+ if (fileInputRef.current) fileInputRef.current.value = "";
3992
+ }
3993
+ };
3994
+ const removeExtra = (id) => {
3995
+ setExtraAttachments((prev) => prev.filter((a) => a.id !== id));
3996
+ };
3997
+ const send = async () => {
3998
+ setSending(true);
3999
+ setError(null);
4000
+ setSuccess(null);
4001
+ try {
4002
+ let attachment;
4003
+ if (emailOpts.attachPdf) {
4004
+ const canvas = document.getElementById(canvasElementId);
4005
+ const pdf = await captureDashboardPdfAttachment(canvas, dashboard.title);
4006
+ attachment = {
4007
+ filename: pdf.filename,
4008
+ contentType: pdf.contentType,
4009
+ contentBase64: pdf.contentBase64
4010
+ };
4011
+ }
4012
+ const extras = emailOpts.allowExtraAttachments ? extraAttachments.slice(0, maxExtra).map(({ filename, contentType, contentBase64 }) => ({
4013
+ filename,
4014
+ contentType,
4015
+ contentBase64
4016
+ })) : [];
4017
+ const plain = htmlToPlainText(bodyHtml);
4018
+ await api.sendDashboardEmail({
4019
+ to,
4020
+ cc: cc.trim() || void 0,
4021
+ subject,
4022
+ body: plain,
4023
+ bodyHtml,
4024
+ dashboardTitle: dashboard.title,
4025
+ attachment,
4026
+ extraAttachments: extras.length ? extras : void 0
4027
+ });
4028
+ setSuccess(t("emailSent"));
4029
+ } catch (err) {
4030
+ setError(err.message);
4031
+ } finally {
4032
+ setSending(false);
4033
+ }
4034
+ };
4035
+ return /* @__PURE__ */ jsx13("div", { className: "dd-modal-backdrop", role: "presentation", onClick: onClose, children: /* @__PURE__ */ jsxs13(
4036
+ "div",
4037
+ {
4038
+ className: "dd-dialog dd-email-panel",
4039
+ role: "dialog",
4040
+ "aria-modal": "true",
4041
+ "aria-labelledby": "dd-email-title",
4042
+ onClick: (e) => e.stopPropagation(),
4043
+ children: [
4044
+ /* @__PURE__ */ jsxs13("header", { className: "dd-dialog-header", children: [
4045
+ /* @__PURE__ */ jsxs13("h2", { id: "dd-email-title", children: [
4046
+ /* @__PURE__ */ jsx13(Mail, { size: ICON_SIZE, "aria-hidden": true }),
4047
+ " ",
4048
+ t("composeEmail")
4049
+ ] }),
4050
+ /* @__PURE__ */ jsx13(
4051
+ "button",
4052
+ {
4053
+ type: "button",
4054
+ className: "dd-btn dd-btn-ghost dd-btn-icon",
4055
+ onClick: onClose,
4056
+ "aria-label": t("cancel"),
4057
+ children: /* @__PURE__ */ jsx13(X7, { size: ICON_SIZE })
4058
+ }
4059
+ )
4060
+ ] }),
4061
+ /* @__PURE__ */ jsxs13("div", { className: "dd-dialog-body dd-email-body-wrap", children: [
4062
+ configured === false && /* @__PURE__ */ jsx13("p", { className: "dd-error", role: "alert", children: t("emailNotConfigured") }),
4063
+ /* @__PURE__ */ jsx13("p", { className: "dd-hint", children: t("emailComposeHint") }),
4064
+ /* @__PURE__ */ jsxs13("div", { className: "dd-field", children: [
4065
+ /* @__PURE__ */ jsx13(FieldLabel, { htmlFor: "dd-email-to", help: t("helpEmailTo"), children: t("emailTo") }),
4066
+ /* @__PURE__ */ jsx13(
4067
+ "input",
4068
+ {
4069
+ id: "dd-email-to",
4070
+ className: "dd-input",
4071
+ type: "text",
4072
+ value: to,
4073
+ onChange: (e) => setTo(e.target.value),
4074
+ placeholder: "name@example.com",
4075
+ autoComplete: "email"
4076
+ }
4077
+ )
4078
+ ] }),
4079
+ /* @__PURE__ */ jsxs13("div", { className: "dd-field", children: [
4080
+ /* @__PURE__ */ jsx13(FieldLabel, { htmlFor: "dd-email-cc", help: t("helpEmailCc"), children: t("emailCc") }),
4081
+ /* @__PURE__ */ jsx13(
4082
+ "input",
4083
+ {
4084
+ id: "dd-email-cc",
4085
+ className: "dd-input",
4086
+ type: "text",
4087
+ value: cc,
4088
+ onChange: (e) => setCc(e.target.value),
4089
+ placeholder: "optional",
4090
+ autoComplete: "email"
4091
+ }
4092
+ )
4093
+ ] }),
4094
+ /* @__PURE__ */ jsxs13("div", { className: "dd-field", children: [
4095
+ /* @__PURE__ */ jsx13(FieldLabel, { htmlFor: "dd-email-subject", children: t("emailSubject") }),
4096
+ /* @__PURE__ */ jsx13(
4097
+ "input",
4098
+ {
4099
+ id: "dd-email-subject",
4100
+ className: "dd-input",
4101
+ type: "text",
4102
+ value: subject,
4103
+ onChange: (e) => setSubject(e.target.value)
4104
+ }
4105
+ )
4106
+ ] }),
4107
+ /* @__PURE__ */ jsxs13("div", { className: "dd-field", children: [
4108
+ /* @__PURE__ */ jsx13(FieldLabel, { htmlFor: "dd-email-body", help: t("helpEmailBody"), children: t("emailBody") }),
4109
+ /* @__PURE__ */ jsx13(
4110
+ RichTextEditor,
4111
+ {
4112
+ id: "dd-email-body",
4113
+ valueHtml: bodyHtml,
4114
+ onChange: setBodyHtml,
4115
+ disabled: sending,
4116
+ placeholder: t("emailBodyPlaceholder")
4117
+ }
4118
+ )
4119
+ ] }),
4120
+ showAttachments && /* @__PURE__ */ jsxs13("div", { className: "dd-field dd-email-attachment-field", children: [
4121
+ /* @__PURE__ */ jsx13(FieldLabel, { help: t("helpEmailAttachment"), children: t("emailAttachments") }),
4122
+ /* @__PURE__ */ jsxs13("ul", { className: "dd-email-attach-list", children: [
4123
+ emailOpts.attachPdf && /* @__PURE__ */ jsxs13("li", { className: "dd-email-attachment", children: [
4124
+ /* @__PURE__ */ jsx13(Paperclip, { size: ICON_SIZE, "aria-hidden": true }),
4125
+ /* @__PURE__ */ jsx13("code", { children: dashboardPdfName }),
4126
+ /* @__PURE__ */ jsx13("span", { className: "dd-muted dd-email-attachment-note", children: t("emailAttachmentPdfNote") })
4127
+ ] }),
4128
+ emailOpts.allowExtraAttachments && extraAttachments.map((file) => /* @__PURE__ */ jsxs13("li", { className: "dd-email-attachment", children: [
4129
+ /* @__PURE__ */ jsx13(Paperclip, { size: ICON_SIZE, "aria-hidden": true }),
4130
+ /* @__PURE__ */ jsx13("code", { title: file.filename, children: file.filename }),
4131
+ /* @__PURE__ */ jsx13(
4132
+ "button",
4133
+ {
4134
+ type: "button",
4135
+ className: "dd-btn dd-btn-ghost dd-btn-icon",
4136
+ title: t("remove"),
4137
+ "aria-label": t("remove"),
4138
+ disabled: sending,
4139
+ onClick: () => removeExtra(file.id),
4140
+ children: /* @__PURE__ */ jsx13(Trash23, { size: ICON_SIZE })
4141
+ }
4142
+ )
4143
+ ] }, file.id))
4144
+ ] }),
4145
+ emailOpts.allowExtraAttachments && maxExtra > 0 && /* @__PURE__ */ jsxs13("div", { className: "dd-email-attach-actions", children: [
4146
+ /* @__PURE__ */ jsx13(
4147
+ "input",
4148
+ {
4149
+ ref: fileInputRef,
4150
+ id: fileInputId,
4151
+ type: "file",
4152
+ multiple: true,
4153
+ className: "dd-visually-hidden",
4154
+ onChange: (e) => void addFiles(e.target.files),
4155
+ disabled: sending || extraAttachments.length >= maxExtra
4156
+ }
4157
+ ),
4158
+ /* @__PURE__ */ jsxs13(
4159
+ "button",
4160
+ {
4161
+ type: "button",
4162
+ className: "dd-btn",
4163
+ disabled: sending || extraAttachments.length >= maxExtra,
4164
+ onClick: () => fileInputRef.current?.click(),
4165
+ children: [
4166
+ /* @__PURE__ */ jsx13(Plus4, { size: ICON_SIZE }),
4167
+ t("emailAddAttachment")
4168
+ ]
4169
+ }
4170
+ ),
4171
+ /* @__PURE__ */ jsx13(FieldHelp, { text: t("helpEmailExtraAttachments") })
4172
+ ] })
4173
+ ] }),
4174
+ error && /* @__PURE__ */ jsx13("p", { className: "dd-error", role: "alert", children: error }),
4175
+ success && /* @__PURE__ */ jsx13("p", { className: "dd-status-ok", role: "status", children: success })
4176
+ ] }),
4177
+ /* @__PURE__ */ jsx13("footer", { className: "dd-dialog-footer", children: /* @__PURE__ */ jsxs13("div", { className: "dd-dialog-footer-end", children: [
4178
+ /* @__PURE__ */ jsx13("button", { type: "button", className: "dd-btn", onClick: onClose, disabled: sending, children: t("cancel") }),
4179
+ /* @__PURE__ */ jsxs13(
4180
+ "button",
4181
+ {
4182
+ type: "button",
4183
+ className: "dd-btn dd-btn-primary",
4184
+ onClick: () => void send(),
4185
+ disabled: sending || !to.trim() || !subject.trim() || configured === false,
4186
+ children: [
4187
+ /* @__PURE__ */ jsx13(Mail, { size: ICON_SIZE }),
4188
+ sending ? t("emailSending") : t("sendEmail")
4189
+ ]
4190
+ }
4191
+ )
4192
+ ] }) })
4193
+ ]
4194
+ }
4195
+ ) });
4196
+ }
4197
+
4198
+ // src/designer/DashboardSettingsPanel.tsx
4199
+ import { useEffect as useEffect11, useMemo as useMemo5, useState as useState10 } from "react";
4200
+ import { Plus as Plus5, RotateCcw as RotateCcw2, Trash2 as Trash24, X as X8 } from "lucide-react";
4201
+ import { useTranslation as useTranslation14 } from "react-i18next";
3540
4202
  import {
3541
4203
  matchThemePreset,
3542
4204
  resolveDashboardTheme as resolveDashboardTheme2,
4205
+ resolveEmailOptions as resolveEmailOptions2,
3543
4206
  resolveExportOptions,
3544
4207
  THEME_PRESETS
3545
4208
  } from "@dashboard-designer/core";
3546
- import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
4209
+ import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
3547
4210
  var CURRENCIES = ["USD", "EUR", "GBP", "INR", "JPY", "CAD", "AUD", "MXN"];
3548
4211
  var TIMEZONES = [
3549
4212
  "UTC",
@@ -3559,6 +4222,7 @@ var TIMEZONES = [
3559
4222
  function draftFromDashboard(dashboard) {
3560
4223
  const theme = resolveDashboardTheme2(dashboard.theme);
3561
4224
  const exportOpts = resolveExportOptions(dashboard.exportOptions);
4225
+ const emailOpts = resolveEmailOptions2(dashboard.exportOptions);
3562
4226
  return {
3563
4227
  locale: dashboard.locale || "en",
3564
4228
  currency: dashboard.currency || "USD",
@@ -3567,9 +4231,16 @@ function draftFromDashboard(dashboard) {
3567
4231
  accent: theme.accent,
3568
4232
  chartColors: [...theme.chartColors],
3569
4233
  allowPrint: exportOpts.print,
3570
- allowPdf: exportOpts.pdf
4234
+ allowPdf: exportOpts.pdf,
4235
+ allowEmail: exportOpts.email,
4236
+ emailAttachPdf: emailOpts.attachPdf,
4237
+ emailAllowExtra: emailOpts.allowExtraAttachments,
4238
+ emailMaxExtra: emailOpts.maxExtraAttachments
3571
4239
  };
3572
4240
  }
4241
+ function isDefaultEmailOptions(draft) {
4242
+ return draft.emailAttachPdf && draft.emailAllowExtra && draft.emailMaxExtra === 5;
4243
+ }
3573
4244
  function normalizeHex(value, fallback) {
3574
4245
  const v = value.trim();
3575
4246
  if (/^#[0-9a-f]{6}$/i.test(v)) return v.toLowerCase();
@@ -3587,9 +4258,9 @@ function DashboardSettingsPanel({
3587
4258
  onClose,
3588
4259
  onChange
3589
4260
  }) {
3590
- const { t } = useTranslation12();
3591
- const [draft, setDraft] = useState9(() => draftFromDashboard(dashboard));
3592
- useEffect9(() => {
4261
+ const { t } = useTranslation14();
4262
+ const [draft, setDraft] = useState10(() => draftFromDashboard(dashboard));
4263
+ useEffect11(() => {
3593
4264
  if (!open) return;
3594
4265
  setDraft(draftFromDashboard(dashboard));
3595
4266
  }, [
@@ -3632,7 +4303,17 @@ function DashboardSettingsPanel({
3632
4303
  )
3633
4304
  };
3634
4305
  const isDefault = matchThemePreset(theme) === "default";
3635
- const exportOptions = draft.allowPrint && draft.allowPdf ? void 0 : { print: draft.allowPrint, pdf: draft.allowPdf };
4306
+ const emailOptions = draft.allowEmail && !isDefaultEmailOptions(draft) ? {
4307
+ attachPdf: draft.emailAttachPdf,
4308
+ allowExtraAttachments: draft.emailAllowExtra,
4309
+ maxExtraAttachments: draft.emailMaxExtra
4310
+ } : void 0;
4311
+ const exportOptions = draft.allowPrint && draft.allowPdf && !draft.allowEmail && !emailOptions ? void 0 : {
4312
+ print: draft.allowPrint,
4313
+ pdf: draft.allowPdf,
4314
+ ...draft.allowEmail ? { email: true } : {},
4315
+ ...emailOptions ? { emailOptions } : {}
4316
+ };
3636
4317
  onChange({
3637
4318
  locale: draft.locale,
3638
4319
  currency: draft.currency,
@@ -3643,7 +4324,7 @@ function DashboardSettingsPanel({
3643
4324
  });
3644
4325
  onClose();
3645
4326
  };
3646
- return /* @__PURE__ */ jsx12("div", { className: "dd-modal-backdrop", role: "presentation", onClick: onClose, children: /* @__PURE__ */ jsxs12(
4327
+ return /* @__PURE__ */ jsx14("div", { className: "dd-modal-backdrop", role: "presentation", onClick: onClose, children: /* @__PURE__ */ jsxs14(
3647
4328
  "div",
3648
4329
  {
3649
4330
  className: "dd-dialog dd-settings-panel",
@@ -3652,25 +4333,25 @@ function DashboardSettingsPanel({
3652
4333
  "aria-labelledby": "dd-settings-title",
3653
4334
  onClick: (e) => e.stopPropagation(),
3654
4335
  children: [
3655
- /* @__PURE__ */ jsxs12("header", { className: "dd-dialog-header", children: [
3656
- /* @__PURE__ */ jsx12("h2", { id: "dd-settings-title", children: t("dashboardSettings") }),
3657
- /* @__PURE__ */ jsx12(
4336
+ /* @__PURE__ */ jsxs14("header", { className: "dd-dialog-header", children: [
4337
+ /* @__PURE__ */ jsx14("h2", { id: "dd-settings-title", children: t("dashboardSettings") }),
4338
+ /* @__PURE__ */ jsx14(
3658
4339
  "button",
3659
4340
  {
3660
4341
  type: "button",
3661
4342
  className: "dd-btn dd-btn-ghost dd-btn-icon",
3662
4343
  onClick: onClose,
3663
4344
  "aria-label": t("cancel"),
3664
- children: /* @__PURE__ */ jsx12(X7, { size: ICON_SIZE })
4345
+ children: /* @__PURE__ */ jsx14(X8, { size: ICON_SIZE })
3665
4346
  }
3666
4347
  )
3667
4348
  ] }),
3668
- /* @__PURE__ */ jsxs12("div", { className: "dd-dialog-body", children: [
3669
- /* @__PURE__ */ jsx12("p", { className: "dd-hint", children: t("dashboardSettingsHint") }),
3670
- /* @__PURE__ */ jsxs12("div", { className: "dd-settings-grid", children: [
3671
- /* @__PURE__ */ jsxs12("div", { className: "dd-field", children: [
3672
- /* @__PURE__ */ jsx12(FieldLabel, { htmlFor: "dd-dash-locale", help: t("helpLocale"), children: t("locale") }),
3673
- /* @__PURE__ */ jsxs12(
4349
+ /* @__PURE__ */ jsxs14("div", { className: "dd-dialog-body", children: [
4350
+ /* @__PURE__ */ jsx14("p", { className: "dd-hint", children: t("dashboardSettingsHint") }),
4351
+ /* @__PURE__ */ jsxs14("div", { className: "dd-settings-grid", children: [
4352
+ /* @__PURE__ */ jsxs14("div", { className: "dd-field", children: [
4353
+ /* @__PURE__ */ jsx14(FieldLabel, { htmlFor: "dd-dash-locale", help: t("helpLocale"), children: t("locale") }),
4354
+ /* @__PURE__ */ jsxs14(
3674
4355
  "select",
3675
4356
  {
3676
4357
  id: "dd-dash-locale",
@@ -3678,30 +4359,30 @@ function DashboardSettingsPanel({
3678
4359
  value: draft.locale,
3679
4360
  onChange: (e) => setDraft((d) => ({ ...d, locale: e.target.value })),
3680
4361
  children: [
3681
- /* @__PURE__ */ jsx12("option", { value: "en", children: "English" }),
3682
- /* @__PURE__ */ jsx12("option", { value: "es", children: "Espa\xF1ol" }),
3683
- /* @__PURE__ */ jsx12("option", { value: "ar", children: "\u0627\u0644\u0639\u0631\u0628\u064A\u0629 (RTL)" }),
3684
- /* @__PURE__ */ jsx12("option", { value: "he", children: "\u05E2\u05D1\u05E8\u05D9\u05EA (RTL)" })
4362
+ /* @__PURE__ */ jsx14("option", { value: "en", children: "English" }),
4363
+ /* @__PURE__ */ jsx14("option", { value: "es", children: "Espa\xF1ol" }),
4364
+ /* @__PURE__ */ jsx14("option", { value: "ar", children: "\u0627\u0644\u0639\u0631\u0628\u064A\u0629 (RTL)" }),
4365
+ /* @__PURE__ */ jsx14("option", { value: "he", children: "\u05E2\u05D1\u05E8\u05D9\u05EA (RTL)" })
3685
4366
  ]
3686
4367
  }
3687
4368
  )
3688
4369
  ] }),
3689
- /* @__PURE__ */ jsxs12("div", { className: "dd-field", children: [
3690
- /* @__PURE__ */ jsx12(FieldLabel, { htmlFor: "dd-dash-currency", help: t("helpCurrency"), children: t("currency") }),
3691
- /* @__PURE__ */ jsx12(
4370
+ /* @__PURE__ */ jsxs14("div", { className: "dd-field", children: [
4371
+ /* @__PURE__ */ jsx14(FieldLabel, { htmlFor: "dd-dash-currency", help: t("helpCurrency"), children: t("currency") }),
4372
+ /* @__PURE__ */ jsx14(
3692
4373
  "select",
3693
4374
  {
3694
4375
  id: "dd-dash-currency",
3695
4376
  className: "dd-input",
3696
4377
  value: draft.currency,
3697
4378
  onChange: (e) => setDraft((d) => ({ ...d, currency: e.target.value })),
3698
- children: CURRENCIES.map((c) => /* @__PURE__ */ jsx12("option", { value: c, children: c }, c))
4379
+ children: CURRENCIES.map((c) => /* @__PURE__ */ jsx14("option", { value: c, children: c }, c))
3699
4380
  }
3700
4381
  )
3701
4382
  ] }),
3702
- /* @__PURE__ */ jsxs12("div", { className: "dd-field", children: [
3703
- /* @__PURE__ */ jsx12(FieldLabel, { htmlFor: "dd-dash-tz", help: t("helpTimeZone"), children: t("timeZone") }),
3704
- /* @__PURE__ */ jsxs12(
4383
+ /* @__PURE__ */ jsxs14("div", { className: "dd-field", children: [
4384
+ /* @__PURE__ */ jsx14(FieldLabel, { htmlFor: "dd-dash-tz", help: t("helpTimeZone"), children: t("timeZone") }),
4385
+ /* @__PURE__ */ jsxs14(
3705
4386
  "select",
3706
4387
  {
3707
4388
  id: "dd-dash-tz",
@@ -3709,15 +4390,15 @@ function DashboardSettingsPanel({
3709
4390
  value: draft.timeZone,
3710
4391
  onChange: (e) => setDraft((d) => ({ ...d, timeZone: e.target.value })),
3711
4392
  children: [
3712
- /* @__PURE__ */ jsx12("option", { value: "", children: t("timeZoneBrowser") }),
3713
- TIMEZONES.map((tz) => /* @__PURE__ */ jsx12("option", { value: tz, children: tz }, tz))
4393
+ /* @__PURE__ */ jsx14("option", { value: "", children: t("timeZoneBrowser") }),
4394
+ TIMEZONES.map((tz) => /* @__PURE__ */ jsx14("option", { value: tz, children: tz }, tz))
3714
4395
  ]
3715
4396
  }
3716
4397
  )
3717
4398
  ] }),
3718
- /* @__PURE__ */ jsxs12("div", { className: "dd-field", children: [
3719
- /* @__PURE__ */ jsx12(FieldLabel, { htmlFor: "dd-dash-dir", help: t("helpTextDirection"), children: t("textDirection") }),
3720
- /* @__PURE__ */ jsxs12(
4399
+ /* @__PURE__ */ jsxs14("div", { className: "dd-field", children: [
4400
+ /* @__PURE__ */ jsx14(FieldLabel, { htmlFor: "dd-dash-dir", help: t("helpTextDirection"), children: t("textDirection") }),
4401
+ /* @__PURE__ */ jsxs14(
3721
4402
  "select",
3722
4403
  {
3723
4404
  id: "dd-dash-dir",
@@ -3725,22 +4406,22 @@ function DashboardSettingsPanel({
3725
4406
  value: draft.dir,
3726
4407
  onChange: (e) => setDraft((d) => ({ ...d, dir: e.target.value })),
3727
4408
  children: [
3728
- /* @__PURE__ */ jsx12("option", { value: "", children: t("dirAuto") }),
3729
- /* @__PURE__ */ jsx12("option", { value: "ltr", children: "LTR" }),
3730
- /* @__PURE__ */ jsx12("option", { value: "rtl", children: "RTL" })
4409
+ /* @__PURE__ */ jsx14("option", { value: "", children: t("dirAuto") }),
4410
+ /* @__PURE__ */ jsx14("option", { value: "ltr", children: "LTR" }),
4411
+ /* @__PURE__ */ jsx14("option", { value: "rtl", children: "RTL" })
3731
4412
  ]
3732
4413
  }
3733
4414
  )
3734
4415
  ] })
3735
4416
  ] }),
3736
- /* @__PURE__ */ jsxs12("div", { className: "dd-theme-section", children: [
3737
- /* @__PURE__ */ jsx12("div", { className: "dd-theme-section-head", children: /* @__PURE__ */ jsxs12("div", { children: [
3738
- /* @__PURE__ */ jsx12("h3", { className: "dd-theme-title", children: t("exportOptions") }),
3739
- /* @__PURE__ */ jsx12("p", { className: "dd-hint dd-theme-hint", children: t("helpExportOptions") })
4417
+ /* @__PURE__ */ jsxs14("div", { className: "dd-theme-section", children: [
4418
+ /* @__PURE__ */ jsx14("div", { className: "dd-theme-section-head", children: /* @__PURE__ */ jsxs14("div", { children: [
4419
+ /* @__PURE__ */ jsx14("h3", { className: "dd-theme-title", children: t("exportOptions") }),
4420
+ /* @__PURE__ */ jsx14("p", { className: "dd-hint dd-theme-hint", children: t("helpExportOptions") })
3740
4421
  ] }) }),
3741
- /* @__PURE__ */ jsxs12("div", { className: "dd-field dd-check-field", children: [
3742
- /* @__PURE__ */ jsxs12("label", { className: "dd-check-row", htmlFor: "dd-export-print", children: [
3743
- /* @__PURE__ */ jsx12(
4422
+ /* @__PURE__ */ jsxs14("div", { className: "dd-field dd-check-field", children: [
4423
+ /* @__PURE__ */ jsxs14("label", { className: "dd-check-row", htmlFor: "dd-export-print", children: [
4424
+ /* @__PURE__ */ jsx14(
3744
4425
  "input",
3745
4426
  {
3746
4427
  id: "dd-export-print",
@@ -3749,13 +4430,13 @@ function DashboardSettingsPanel({
3749
4430
  onChange: (e) => setDraft((d) => ({ ...d, allowPrint: e.target.checked }))
3750
4431
  }
3751
4432
  ),
3752
- /* @__PURE__ */ jsx12("span", { className: "dd-check-row-text", children: t("allowPrint") })
4433
+ /* @__PURE__ */ jsx14("span", { className: "dd-check-row-text", children: t("allowPrint") })
3753
4434
  ] }),
3754
- /* @__PURE__ */ jsx12(FieldHelp, { text: t("helpAllowPrint") })
4435
+ /* @__PURE__ */ jsx14(FieldHelp, { text: t("helpAllowPrint") })
3755
4436
  ] }),
3756
- /* @__PURE__ */ jsxs12("div", { className: "dd-field dd-check-field", children: [
3757
- /* @__PURE__ */ jsxs12("label", { className: "dd-check-row", htmlFor: "dd-export-pdf", children: [
3758
- /* @__PURE__ */ jsx12(
4437
+ /* @__PURE__ */ jsxs14("div", { className: "dd-field dd-check-field", children: [
4438
+ /* @__PURE__ */ jsxs14("label", { className: "dd-check-row", htmlFor: "dd-export-pdf", children: [
4439
+ /* @__PURE__ */ jsx14(
3759
4440
  "input",
3760
4441
  {
3761
4442
  id: "dd-export-pdf",
@@ -3764,18 +4445,87 @@ function DashboardSettingsPanel({
3764
4445
  onChange: (e) => setDraft((d) => ({ ...d, allowPdf: e.target.checked }))
3765
4446
  }
3766
4447
  ),
3767
- /* @__PURE__ */ jsx12("span", { className: "dd-check-row-text", children: t("allowExportPdf") })
4448
+ /* @__PURE__ */ jsx14("span", { className: "dd-check-row-text", children: t("allowExportPdf") })
4449
+ ] }),
4450
+ /* @__PURE__ */ jsx14(FieldHelp, { text: t("helpAllowExportPdf") })
4451
+ ] }),
4452
+ /* @__PURE__ */ jsxs14("div", { className: "dd-field dd-check-field", children: [
4453
+ /* @__PURE__ */ jsxs14("label", { className: "dd-check-row", htmlFor: "dd-export-email", children: [
4454
+ /* @__PURE__ */ jsx14(
4455
+ "input",
4456
+ {
4457
+ id: "dd-export-email",
4458
+ type: "checkbox",
4459
+ checked: draft.allowEmail,
4460
+ onChange: (e) => setDraft((d) => ({ ...d, allowEmail: e.target.checked }))
4461
+ }
4462
+ ),
4463
+ /* @__PURE__ */ jsx14("span", { className: "dd-check-row-text", children: t("allowEmail") })
4464
+ ] }),
4465
+ /* @__PURE__ */ jsx14(FieldHelp, { text: t("helpAllowEmail") })
4466
+ ] }),
4467
+ draft.allowEmail && /* @__PURE__ */ jsxs14("div", { className: "dd-email-options", children: [
4468
+ /* @__PURE__ */ jsx14("p", { className: "dd-hint dd-email-options-title", children: t("emailAttachmentOptions") }),
4469
+ /* @__PURE__ */ jsxs14("div", { className: "dd-field dd-check-field", children: [
4470
+ /* @__PURE__ */ jsxs14("label", { className: "dd-check-row", htmlFor: "dd-email-attach-pdf", children: [
4471
+ /* @__PURE__ */ jsx14(
4472
+ "input",
4473
+ {
4474
+ id: "dd-email-attach-pdf",
4475
+ type: "checkbox",
4476
+ checked: draft.emailAttachPdf,
4477
+ onChange: (e) => setDraft((d) => ({ ...d, emailAttachPdf: e.target.checked }))
4478
+ }
4479
+ ),
4480
+ /* @__PURE__ */ jsx14("span", { className: "dd-check-row-text", children: t("emailAttachPdf") })
4481
+ ] }),
4482
+ /* @__PURE__ */ jsx14(FieldHelp, { text: t("helpEmailAttachPdf") })
4483
+ ] }),
4484
+ /* @__PURE__ */ jsxs14("div", { className: "dd-field dd-check-field", children: [
4485
+ /* @__PURE__ */ jsxs14("label", { className: "dd-check-row", htmlFor: "dd-email-allow-extra", children: [
4486
+ /* @__PURE__ */ jsx14(
4487
+ "input",
4488
+ {
4489
+ id: "dd-email-allow-extra",
4490
+ type: "checkbox",
4491
+ checked: draft.emailAllowExtra,
4492
+ onChange: (e) => setDraft((d) => ({ ...d, emailAllowExtra: e.target.checked }))
4493
+ }
4494
+ ),
4495
+ /* @__PURE__ */ jsx14("span", { className: "dd-check-row-text", children: t("emailAllowExtraAttachments") })
4496
+ ] }),
4497
+ /* @__PURE__ */ jsx14(FieldHelp, { text: t("helpEmailAllowExtraAttachments") })
3768
4498
  ] }),
3769
- /* @__PURE__ */ jsx12(FieldHelp, { text: t("helpAllowExportPdf") })
4499
+ draft.emailAllowExtra && /* @__PURE__ */ jsxs14("div", { className: "dd-field", children: [
4500
+ /* @__PURE__ */ jsx14(FieldLabel, { htmlFor: "dd-email-max-extra", help: t("helpEmailMaxExtraAttachments"), children: t("emailMaxExtraAttachments") }),
4501
+ /* @__PURE__ */ jsx14(
4502
+ "input",
4503
+ {
4504
+ id: "dd-email-max-extra",
4505
+ className: "dd-input",
4506
+ type: "number",
4507
+ min: 0,
4508
+ max: 10,
4509
+ value: draft.emailMaxExtra,
4510
+ onChange: (e) => {
4511
+ const n = Number(e.target.value);
4512
+ setDraft((d) => ({
4513
+ ...d,
4514
+ emailMaxExtra: Number.isFinite(n) ? Math.max(0, Math.min(10, Math.floor(n))) : 5
4515
+ }));
4516
+ }
4517
+ }
4518
+ )
4519
+ ] })
3770
4520
  ] })
3771
4521
  ] }),
3772
- /* @__PURE__ */ jsxs12("div", { className: "dd-theme-section", children: [
3773
- /* @__PURE__ */ jsxs12("div", { className: "dd-theme-section-head", children: [
3774
- /* @__PURE__ */ jsxs12("div", { children: [
3775
- /* @__PURE__ */ jsx12("h3", { className: "dd-theme-title", children: t("themeColors") }),
3776
- /* @__PURE__ */ jsx12("p", { className: "dd-hint dd-theme-hint", children: t("helpThemeColors") })
4522
+ /* @__PURE__ */ jsxs14("div", { className: "dd-theme-section", children: [
4523
+ /* @__PURE__ */ jsxs14("div", { className: "dd-theme-section-head", children: [
4524
+ /* @__PURE__ */ jsxs14("div", { children: [
4525
+ /* @__PURE__ */ jsx14("h3", { className: "dd-theme-title", children: t("themeColors") }),
4526
+ /* @__PURE__ */ jsx14("p", { className: "dd-hint dd-theme-hint", children: t("helpThemeColors") })
3777
4527
  ] }),
3778
- /* @__PURE__ */ jsxs12(
4528
+ /* @__PURE__ */ jsxs14(
3779
4529
  "button",
3780
4530
  {
3781
4531
  type: "button",
@@ -3783,15 +4533,15 @@ function DashboardSettingsPanel({
3783
4533
  onClick: resetTheme,
3784
4534
  title: t("resetTheme"),
3785
4535
  children: [
3786
- /* @__PURE__ */ jsx12(RotateCcw2, { size: ICON_SIZE }),
4536
+ /* @__PURE__ */ jsx14(RotateCcw2, { size: ICON_SIZE }),
3787
4537
  t("resetTheme")
3788
4538
  ]
3789
4539
  }
3790
4540
  )
3791
4541
  ] }),
3792
- /* @__PURE__ */ jsxs12("div", { className: "dd-field", children: [
3793
- /* @__PURE__ */ jsx12(FieldLabel, { htmlFor: "dd-theme-preset", help: t("helpThemePreset"), children: t("themePreset") }),
3794
- /* @__PURE__ */ jsxs12(
4542
+ /* @__PURE__ */ jsxs14("div", { className: "dd-field", children: [
4543
+ /* @__PURE__ */ jsx14(FieldLabel, { htmlFor: "dd-theme-preset", help: t("helpThemePreset"), children: t("themePreset") }),
4544
+ /* @__PURE__ */ jsxs14(
3795
4545
  "select",
3796
4546
  {
3797
4547
  id: "dd-theme-preset",
@@ -3802,19 +4552,19 @@ function DashboardSettingsPanel({
3802
4552
  applyPreset(e.target.value);
3803
4553
  },
3804
4554
  children: [
3805
- /* @__PURE__ */ jsx12("option", { value: "default", children: t("themePresetDefault") }),
3806
- /* @__PURE__ */ jsx12("option", { value: "ocean", children: t("themePresetOcean") }),
3807
- /* @__PURE__ */ jsx12("option", { value: "sunset", children: t("themePresetSunset") }),
3808
- /* @__PURE__ */ jsx12("option", { value: "slate", children: t("themePresetSlate") }),
3809
- /* @__PURE__ */ jsx12("option", { value: "custom", disabled: presetId !== "custom", children: t("themePresetCustom") })
4555
+ /* @__PURE__ */ jsx14("option", { value: "default", children: t("themePresetDefault") }),
4556
+ /* @__PURE__ */ jsx14("option", { value: "ocean", children: t("themePresetOcean") }),
4557
+ /* @__PURE__ */ jsx14("option", { value: "sunset", children: t("themePresetSunset") }),
4558
+ /* @__PURE__ */ jsx14("option", { value: "slate", children: t("themePresetSlate") }),
4559
+ /* @__PURE__ */ jsx14("option", { value: "custom", disabled: presetId !== "custom", children: t("themePresetCustom") })
3810
4560
  ]
3811
4561
  }
3812
4562
  )
3813
4563
  ] }),
3814
- /* @__PURE__ */ jsxs12("div", { className: "dd-field", children: [
3815
- /* @__PURE__ */ jsx12(FieldLabel, { htmlFor: "dd-theme-accent", help: t("helpThemeAccent"), children: t("themeAccent") }),
3816
- /* @__PURE__ */ jsxs12("div", { className: "dd-color-row", children: [
3817
- /* @__PURE__ */ jsx12(
4564
+ /* @__PURE__ */ jsxs14("div", { className: "dd-field", children: [
4565
+ /* @__PURE__ */ jsx14(FieldLabel, { htmlFor: "dd-theme-accent", help: t("helpThemeAccent"), children: t("themeAccent") }),
4566
+ /* @__PURE__ */ jsxs14("div", { className: "dd-color-row", children: [
4567
+ /* @__PURE__ */ jsx14(
3818
4568
  "input",
3819
4569
  {
3820
4570
  id: "dd-theme-accent",
@@ -3825,7 +4575,7 @@ function DashboardSettingsPanel({
3825
4575
  "aria-label": t("themeAccent")
3826
4576
  }
3827
4577
  ),
3828
- /* @__PURE__ */ jsx12(
4578
+ /* @__PURE__ */ jsx14(
3829
4579
  "input",
3830
4580
  {
3831
4581
  className: "dd-input dd-color-hex",
@@ -3836,10 +4586,10 @@ function DashboardSettingsPanel({
3836
4586
  )
3837
4587
  ] })
3838
4588
  ] }),
3839
- /* @__PURE__ */ jsxs12("div", { className: "dd-field", children: [
3840
- /* @__PURE__ */ jsx12(FieldLabel, { help: t("helpThemeChartColors"), children: t("themeChartColors") }),
3841
- /* @__PURE__ */ jsx12("div", { className: "dd-chart-color-list", children: draft.chartColors.map((color, index) => /* @__PURE__ */ jsxs12("div", { className: "dd-color-row", children: [
3842
- /* @__PURE__ */ jsx12(
4589
+ /* @__PURE__ */ jsxs14("div", { className: "dd-field", children: [
4590
+ /* @__PURE__ */ jsx14(FieldLabel, { help: t("helpThemeChartColors"), children: t("themeChartColors") }),
4591
+ /* @__PURE__ */ jsx14("div", { className: "dd-chart-color-list", children: draft.chartColors.map((color, index) => /* @__PURE__ */ jsxs14("div", { className: "dd-color-row", children: [
4592
+ /* @__PURE__ */ jsx14(
3843
4593
  "input",
3844
4594
  {
3845
4595
  type: "color",
@@ -3856,7 +4606,7 @@ function DashboardSettingsPanel({
3856
4606
  "aria-label": t("themeChartColorN", { n: index + 1 })
3857
4607
  }
3858
4608
  ),
3859
- /* @__PURE__ */ jsx12(
4609
+ /* @__PURE__ */ jsx14(
3860
4610
  "input",
3861
4611
  {
3862
4612
  className: "dd-input dd-color-hex",
@@ -3869,7 +4619,7 @@ function DashboardSettingsPanel({
3869
4619
  spellCheck: false
3870
4620
  }
3871
4621
  ),
3872
- /* @__PURE__ */ jsx12(
4622
+ /* @__PURE__ */ jsx14(
3873
4623
  "button",
3874
4624
  {
3875
4625
  type: "button",
@@ -3881,11 +4631,11 @@ function DashboardSettingsPanel({
3881
4631
  ...d,
3882
4632
  chartColors: d.chartColors.filter((_, i) => i !== index)
3883
4633
  })),
3884
- children: /* @__PURE__ */ jsx12(Trash23, { size: ICON_SIZE })
4634
+ children: /* @__PURE__ */ jsx14(Trash24, { size: ICON_SIZE })
3885
4635
  }
3886
4636
  )
3887
4637
  ] }, index)) }),
3888
- /* @__PURE__ */ jsxs12(
4638
+ /* @__PURE__ */ jsxs14(
3889
4639
  "button",
3890
4640
  {
3891
4641
  type: "button",
@@ -3898,12 +4648,12 @@ function DashboardSettingsPanel({
3898
4648
  ]
3899
4649
  })),
3900
4650
  children: [
3901
- /* @__PURE__ */ jsx12(Plus4, { size: ICON_SIZE }),
4651
+ /* @__PURE__ */ jsx14(Plus5, { size: ICON_SIZE }),
3902
4652
  t("addColor")
3903
4653
  ]
3904
4654
  }
3905
4655
  ),
3906
- /* @__PURE__ */ jsx12("div", { className: "dd-theme-preview", "aria-hidden": true, children: draft.chartColors.map((c, i) => /* @__PURE__ */ jsx12(
4656
+ /* @__PURE__ */ jsx14("div", { className: "dd-theme-preview", "aria-hidden": true, children: draft.chartColors.map((c, i) => /* @__PURE__ */ jsx14(
3907
4657
  "span",
3908
4658
  {
3909
4659
  className: "dd-theme-preview-chip",
@@ -3914,9 +4664,9 @@ function DashboardSettingsPanel({
3914
4664
  ] })
3915
4665
  ] })
3916
4666
  ] }),
3917
- /* @__PURE__ */ jsxs12("footer", { className: "dd-dialog-footer dd-dialog-footer-end", children: [
3918
- /* @__PURE__ */ jsx12("button", { type: "button", className: "dd-btn", onClick: onClose, children: t("cancel") }),
3919
- /* @__PURE__ */ jsx12("button", { type: "button", className: "dd-btn dd-btn-primary", onClick: apply, children: t("apply") })
4667
+ /* @__PURE__ */ jsxs14("footer", { className: "dd-dialog-footer dd-dialog-footer-end", children: [
4668
+ /* @__PURE__ */ jsx14("button", { type: "button", className: "dd-btn", onClick: onClose, children: t("cancel") }),
4669
+ /* @__PURE__ */ jsx14("button", { type: "button", className: "dd-btn dd-btn-primary", onClick: apply, children: t("apply") })
3920
4670
  ] })
3921
4671
  ]
3922
4672
  }
@@ -3924,7 +4674,7 @@ function DashboardSettingsPanel({
3924
4674
  }
3925
4675
 
3926
4676
  // src/designer/useDashboardHistory.ts
3927
- import { useCallback, useEffect as useEffect10, useRef as useRef6, useState as useState10 } from "react";
4677
+ import { useCallback, useEffect as useEffect12, useRef as useRef8, useState as useState11 } from "react";
3928
4678
  var MAX_HISTORY = 50;
3929
4679
  function cloneDashboard(dashboard) {
3930
4680
  return structuredClone(dashboard);
@@ -3933,22 +4683,22 @@ function dashboardsEqual(a, b) {
3933
4683
  return JSON.stringify(a) === JSON.stringify(b);
3934
4684
  }
3935
4685
  function useDashboardHistory(initial, onChange, readOnly = false) {
3936
- const [dashboard, setDashboard] = useState10(() => cloneDashboard(initial));
3937
- const [past, setPast] = useState10([]);
3938
- const [future, setFuture] = useState10([]);
3939
- const [baseline, setBaseline] = useState10(() => cloneDashboard(initial));
3940
- const presentRef = useRef6(dashboard);
3941
- const pastRef = useRef6(past);
3942
- const futureRef = useRef6(future);
3943
- const baselineRef = useRef6(baseline);
3944
- const skipNextExternalSync = useRef6(false);
3945
- const layoutBeforeRef = useRef6(null);
3946
- const suppressLayoutWriteRef = useRef6(false);
4686
+ const [dashboard, setDashboard] = useState11(() => cloneDashboard(initial));
4687
+ const [past, setPast] = useState11([]);
4688
+ const [future, setFuture] = useState11([]);
4689
+ const [baseline, setBaseline] = useState11(() => cloneDashboard(initial));
4690
+ const presentRef = useRef8(dashboard);
4691
+ const pastRef = useRef8(past);
4692
+ const futureRef = useRef8(future);
4693
+ const baselineRef = useRef8(baseline);
4694
+ const skipNextExternalSync = useRef8(false);
4695
+ const layoutBeforeRef = useRef8(null);
4696
+ const suppressLayoutWriteRef = useRef8(false);
3947
4697
  presentRef.current = dashboard;
3948
4698
  pastRef.current = past;
3949
4699
  futureRef.current = future;
3950
4700
  baselineRef.current = baseline;
3951
- useEffect10(() => {
4701
+ useEffect12(() => {
3952
4702
  if (skipNextExternalSync.current) {
3953
4703
  skipNextExternalSync.current = false;
3954
4704
  return;
@@ -3970,7 +4720,7 @@ function useDashboardHistory(initial, onChange, readOnly = false) {
3970
4720
  setDashboard(next);
3971
4721
  }
3972
4722
  }, [initial]);
3973
- useEffect10(() => {
4723
+ useEffect12(() => {
3974
4724
  if (readOnly) return;
3975
4725
  setBaseline(cloneDashboard(presentRef.current));
3976
4726
  pastRef.current = [];
@@ -3978,7 +4728,7 @@ function useDashboardHistory(initial, onChange, readOnly = false) {
3978
4728
  setPast([]);
3979
4729
  setFuture([]);
3980
4730
  }, [readOnly]);
3981
- useEffect10(() => {
4731
+ useEffect12(() => {
3982
4732
  if (!suppressLayoutWriteRef.current) return;
3983
4733
  let inner = 0;
3984
4734
  const outer = requestAnimationFrame(() => {
@@ -4133,7 +4883,7 @@ function useDashboardHistory(initial, onChange, readOnly = false) {
4133
4883
  // src/designer/DashboardDesigner.tsx
4134
4884
  import "react-grid-layout/css/styles.css";
4135
4885
  import "react-resizable/css/styles.css";
4136
- import { Fragment as Fragment5, jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
4886
+ import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
4137
4887
  function listboxFilterField(widget) {
4138
4888
  const override = widget.chartOptions.filterField;
4139
4889
  if (typeof override === "string" && override.trim()) return override.trim();
@@ -4189,25 +4939,25 @@ function WidgetCard({
4189
4939
  listboxSelection,
4190
4940
  onListboxSelectionChange
4191
4941
  }) {
4192
- const { t } = useTranslation13();
4942
+ const { t } = useTranslation15();
4193
4943
  const { data, error, loading } = useWidgetData(api, widget, globalFilters, refreshKey);
4194
4944
  const TypeIcon = chartTypeIcon[widget.type];
4195
4945
  const stopDrag = (e) => {
4196
4946
  e.stopPropagation();
4197
4947
  };
4198
- return /* @__PURE__ */ jsxs13("div", { className: `dd-widget ${selected ? "selected" : ""}`, onClick: onSelect, role: "presentation", children: [
4199
- /* @__PURE__ */ jsxs13("div", { className: "dd-widget-header", children: [
4200
- /* @__PURE__ */ jsxs13("div", { className: readOnly ? "dd-widget-title-wrap" : "dd-widget-drag", children: [
4201
- !readOnly && /* @__PURE__ */ jsx13("span", { className: "dd-widget-drag-grip", "aria-hidden": true }),
4202
- /* @__PURE__ */ jsx13("span", { className: "dd-widget-title", children: widget.title })
4948
+ return /* @__PURE__ */ jsxs15("div", { className: `dd-widget ${selected ? "selected" : ""}`, onClick: onSelect, role: "presentation", children: [
4949
+ /* @__PURE__ */ jsxs15("div", { className: "dd-widget-header", children: [
4950
+ /* @__PURE__ */ jsxs15("div", { className: readOnly ? "dd-widget-title-wrap" : "dd-widget-drag", children: [
4951
+ !readOnly && /* @__PURE__ */ jsx15("span", { className: "dd-widget-drag-grip", "aria-hidden": true }),
4952
+ /* @__PURE__ */ jsx15("span", { className: "dd-widget-title", children: widget.title })
4203
4953
  ] }),
4204
- /* @__PURE__ */ jsxs13("div", { className: "dd-widget-header-actions", children: [
4205
- /* @__PURE__ */ jsxs13("span", { className: "dd-badge", title: widget.type, children: [
4206
- /* @__PURE__ */ jsx13(TypeIcon, { size: 12, strokeWidth: 2.25, "aria-hidden": true }),
4954
+ /* @__PURE__ */ jsxs15("div", { className: "dd-widget-header-actions", children: [
4955
+ /* @__PURE__ */ jsxs15("span", { className: "dd-badge", title: widget.type, children: [
4956
+ /* @__PURE__ */ jsx15(TypeIcon, { size: 12, strokeWidth: 2.25, "aria-hidden": true }),
4207
4957
  widget.type
4208
4958
  ] }),
4209
- !readOnly && /* @__PURE__ */ jsxs13(Fragment5, { children: [
4210
- /* @__PURE__ */ jsx13(
4959
+ !readOnly && /* @__PURE__ */ jsxs15(Fragment5, { children: [
4960
+ /* @__PURE__ */ jsx15(
4211
4961
  "button",
4212
4962
  {
4213
4963
  type: "button",
@@ -4221,10 +4971,10 @@ function WidgetCard({
4221
4971
  e.stopPropagation();
4222
4972
  onEdit();
4223
4973
  },
4224
- children: /* @__PURE__ */ jsx13(Pencil, { size: ICON_SIZE })
4974
+ children: /* @__PURE__ */ jsx15(Pencil, { size: ICON_SIZE })
4225
4975
  }
4226
4976
  ),
4227
- /* @__PURE__ */ jsx13(
4977
+ /* @__PURE__ */ jsx15(
4228
4978
  "button",
4229
4979
  {
4230
4980
  type: "button",
@@ -4238,13 +4988,13 @@ function WidgetCard({
4238
4988
  e.stopPropagation();
4239
4989
  onDelete();
4240
4990
  },
4241
- children: /* @__PURE__ */ jsx13(Trash24, { size: ICON_SIZE })
4991
+ children: /* @__PURE__ */ jsx15(Trash25, { size: ICON_SIZE })
4242
4992
  }
4243
4993
  )
4244
4994
  ] })
4245
4995
  ] })
4246
4996
  ] }),
4247
- /* @__PURE__ */ jsxs13(
4997
+ /* @__PURE__ */ jsxs15(
4248
4998
  "div",
4249
4999
  {
4250
5000
  className: "dd-widget-body",
@@ -4254,8 +5004,8 @@ function WidgetCard({
4254
5004
  onEdit();
4255
5005
  },
4256
5006
  children: [
4257
- error && /* @__PURE__ */ jsx13("div", { className: "dd-error", children: error }),
4258
- !error && /* @__PURE__ */ jsx13(
5007
+ error && /* @__PURE__ */ jsx15("div", { className: "dd-error", children: error }),
5008
+ !error && /* @__PURE__ */ jsx15(
4259
5009
  ChartRenderer,
4260
5010
  {
4261
5011
  widget,
@@ -4286,7 +5036,7 @@ function DashboardDesigner({
4286
5036
  onToggleEdit
4287
5037
  }) {
4288
5038
  ensureI18n(locale);
4289
- const { t, i18n: i18n2 } = useTranslation13();
5039
+ const { t, i18n: i18n2 } = useTranslation15();
4290
5040
  const api = useMemo6(() => new DashboardApiClient(apiBaseUrl, auth), [apiBaseUrl, auth]);
4291
5041
  const seed = useMemo6(() => initial ?? createEmptyDashboard(), [initial]);
4292
5042
  const {
@@ -4306,19 +5056,19 @@ function DashboardDesigner({
4306
5056
  canRedo,
4307
5057
  canReset
4308
5058
  } = useDashboardHistory(seed, onChange, readOnly);
4309
- const [editingTitle, setEditingTitle] = useState11(false);
4310
- const [historyOpen, setHistoryOpen] = useState11(false);
4311
- const [filtersPanel, setFiltersPanel] = useState11(DEFAULT_FILTERS_PANEL);
4312
- const titleInputRef = React11.useRef(null);
4313
- const titleFocusSnapshot = React11.useRef(null);
4314
- useEffect11(() => {
5059
+ const [editingTitle, setEditingTitle] = useState12(false);
5060
+ const [historyOpen, setHistoryOpen] = useState12(false);
5061
+ const [filtersPanel, setFiltersPanel] = useState12(DEFAULT_FILTERS_PANEL);
5062
+ const titleInputRef = React13.useRef(null);
5063
+ const titleFocusSnapshot = React13.useRef(null);
5064
+ useEffect13(() => {
4315
5065
  if (!editingTitle) return;
4316
5066
  const el = titleInputRef.current;
4317
5067
  if (!el) return;
4318
5068
  el.focus();
4319
5069
  el.select();
4320
5070
  }, [editingTitle]);
4321
- useEffect11(() => {
5071
+ useEffect13(() => {
4322
5072
  if (readOnly) {
4323
5073
  setEditingTitle(false);
4324
5074
  setHistoryOpen(false);
@@ -4326,18 +5076,19 @@ function DashboardDesigner({
4326
5076
  setSettingsOpen(false);
4327
5077
  }
4328
5078
  }, [readOnly]);
4329
- const [selectedId, setSelectedId] = useState11(null);
4330
- const [connections, setConnections] = useState11([]);
4331
- const [refreshKey, setRefreshKey] = useState11(0);
4332
- const [width, setWidth] = useState11(900);
4333
- const [drillTrail, setDrillTrail] = useState11([]);
4334
- const [modalOpen, setModalOpen] = useState11(false);
4335
- const [modalMode, setModalMode] = useState11("add");
4336
- const [editingWidget, setEditingWidget] = useState11(null);
4337
- const [shareOpen, setShareOpen] = useState11(false);
4338
- const [settingsOpen, setSettingsOpen] = useState11(false);
4339
- const [drillError, setDrillError] = useState11(null);
4340
- const [listboxSelections, setListboxSelections] = useState11({});
5079
+ const [selectedId, setSelectedId] = useState12(null);
5080
+ const [connections, setConnections] = useState12([]);
5081
+ const [refreshKey, setRefreshKey] = useState12(0);
5082
+ const [width, setWidth] = useState12(900);
5083
+ const [drillTrail, setDrillTrail] = useState12([]);
5084
+ const [modalOpen, setModalOpen] = useState12(false);
5085
+ const [modalMode, setModalMode] = useState12("add");
5086
+ const [editingWidget, setEditingWidget] = useState12(null);
5087
+ const [shareOpen, setShareOpen] = useState12(false);
5088
+ const [emailOpen, setEmailOpen] = useState12(false);
5089
+ const [settingsOpen, setSettingsOpen] = useState12(false);
5090
+ const [drillError, setDrillError] = useState12(null);
5091
+ const [listboxSelections, setListboxSelections] = useState12({});
4341
5092
  const effectiveReadOnly = readOnly || drillTrail.length > 0;
4342
5093
  const textDir = resolveDashboardDir(dashboard);
4343
5094
  const effectiveLocale = dashboard.locale || locale;
@@ -4350,7 +5101,7 @@ function DashboardDesigner({
4350
5101
  const handleExportPdf = () => {
4351
5102
  exportDashboardPdf(document.getElementById("dd-canvas"), dashboard.title);
4352
5103
  };
4353
- useEffect11(() => {
5104
+ useEffect13(() => {
4354
5105
  setListboxSelections({});
4355
5106
  }, [dashboard.id]);
4356
5107
  const effectiveFiltersFor = (widget) => {
@@ -4362,13 +5113,13 @@ function DashboardDesigner({
4362
5113
  const globals = dashboard.globalFilters.filter((f) => !f.id.startsWith("listbox:"));
4363
5114
  return [...globals, ...fromListboxes];
4364
5115
  };
4365
- useEffect11(() => {
5116
+ useEffect13(() => {
4366
5117
  void i18n2.changeLanguage(effectiveLocale);
4367
5118
  }, [effectiveLocale, i18n2]);
4368
- useEffect11(() => {
5119
+ useEffect13(() => {
4369
5120
  api.listConnections().then((r) => setConnections(r.connections)).catch(() => setConnections([]));
4370
5121
  }, [api]);
4371
- useEffect11(() => {
5122
+ useEffect13(() => {
4372
5123
  const el = document.getElementById("dd-canvas");
4373
5124
  if (!el) return;
4374
5125
  const measure = () => {
@@ -4381,7 +5132,7 @@ function DashboardDesigner({
4381
5132
  measure();
4382
5133
  return () => ro.disconnect();
4383
5134
  }, []);
4384
- useEffect11(() => {
5135
+ useEffect13(() => {
4385
5136
  if (effectiveReadOnly) return;
4386
5137
  const onKeyDown = (e) => {
4387
5138
  const target = e.target;
@@ -4582,16 +5333,16 @@ function DashboardDesigner({
4582
5333
  }
4583
5334
  setRefreshKey((k) => k + 1);
4584
5335
  };
4585
- return /* @__PURE__ */ jsxs13(
5336
+ return /* @__PURE__ */ jsxs15(
4586
5337
  "div",
4587
5338
  {
4588
5339
  className: `dd-root ${effectiveReadOnly ? "dd-readonly" : ""} ${filtersPanel.open || filtersPanel.collapsed ? "dd-has-filters-side" : ""} ${filtersPanel.collapsed ? "dd-filters-side-is-collapsed" : ""} ${filtersPanel.pinned ? "dd-filters-side-is-pinned" : ""}`,
4589
5340
  dir: textDir,
4590
5341
  style: themeStyle,
4591
5342
  children: [
4592
- /* @__PURE__ */ jsxs13("header", { className: "dd-toolbar", children: [
4593
- /* @__PURE__ */ jsxs13("div", { className: "dd-toolbar-left", children: [
4594
- editingTitle && !effectiveReadOnly ? /* @__PURE__ */ jsx13(
5343
+ /* @__PURE__ */ jsxs15("header", { className: "dd-toolbar", children: [
5344
+ /* @__PURE__ */ jsxs15("div", { className: "dd-toolbar-left", children: [
5345
+ editingTitle && !effectiveReadOnly ? /* @__PURE__ */ jsx15(
4595
5346
  "input",
4596
5347
  {
4597
5348
  ref: titleInputRef,
@@ -4621,7 +5372,7 @@ function DashboardDesigner({
4621
5372
  }
4622
5373
  }
4623
5374
  }
4624
- ) : /* @__PURE__ */ jsx13(
5375
+ ) : /* @__PURE__ */ jsx15(
4625
5376
  "button",
4626
5377
  {
4627
5378
  type: "button",
@@ -4635,19 +5386,19 @@ function DashboardDesigner({
4635
5386
  children: dashboard.title || t("title")
4636
5387
  }
4637
5388
  ),
4638
- drillTrail.length > 0 && /* @__PURE__ */ jsxs13("span", { className: "dd-drill-crumb dd-muted", children: [
5389
+ drillTrail.length > 0 && /* @__PURE__ */ jsxs15("span", { className: "dd-drill-crumb dd-muted", children: [
4639
5390
  drillTrail.map((f) => f.title).join(" \u203A "),
4640
5391
  " \u203A ",
4641
5392
  dashboard.title
4642
5393
  ] })
4643
5394
  ] }),
4644
- /* @__PURE__ */ jsxs13("div", { className: "dd-toolbar-right", children: [
4645
- drillError && /* @__PURE__ */ jsx13("span", { className: "dd-error dd-inline-error", children: drillError }),
4646
- drillTrail.length > 0 && /* @__PURE__ */ jsxs13("button", { type: "button", className: "dd-btn", onClick: handleDrillBack, children: [
4647
- /* @__PURE__ */ jsx13(ArrowUpLeft, { size: ICON_SIZE }),
5395
+ /* @__PURE__ */ jsxs15("div", { className: "dd-toolbar-right", children: [
5396
+ drillError && /* @__PURE__ */ jsx15("span", { className: "dd-error dd-inline-error", children: drillError }),
5397
+ drillTrail.length > 0 && /* @__PURE__ */ jsxs15("button", { type: "button", className: "dd-btn", onClick: handleDrillBack, children: [
5398
+ /* @__PURE__ */ jsx15(ArrowUpLeft, { size: ICON_SIZE }),
4648
5399
  t("drillBack")
4649
5400
  ] }),
4650
- /* @__PURE__ */ jsx13(
5401
+ /* @__PURE__ */ jsx15(
4651
5402
  GlobalFiltersPopover,
4652
5403
  {
4653
5404
  filters: dashboard.globalFilters,
@@ -4657,8 +5408,8 @@ function DashboardDesigner({
4657
5408
  onPanelChange: setFiltersPanel
4658
5409
  }
4659
5410
  ),
4660
- (exportOpts.print || exportOpts.pdf) && /* @__PURE__ */ jsxs13("div", { className: "dd-toolbar-export", role: "group", "aria-label": t("exportOptions"), children: [
4661
- exportOpts.print && /* @__PURE__ */ jsxs13(
5411
+ (exportOpts.print || exportOpts.pdf || exportOpts.email) && /* @__PURE__ */ jsxs15("div", { className: "dd-toolbar-export", role: "group", "aria-label": t("exportOptions"), children: [
5412
+ exportOpts.print && /* @__PURE__ */ jsxs15(
4662
5413
  "button",
4663
5414
  {
4664
5415
  type: "button",
@@ -4667,12 +5418,12 @@ function DashboardDesigner({
4667
5418
  "aria-label": t("printDashboard"),
4668
5419
  onClick: handlePrint,
4669
5420
  children: [
4670
- /* @__PURE__ */ jsx13(Printer, { size: ICON_SIZE }),
5421
+ /* @__PURE__ */ jsx15(Printer, { size: ICON_SIZE }),
4671
5422
  t("print")
4672
5423
  ]
4673
5424
  }
4674
5425
  ),
4675
- exportOpts.pdf && /* @__PURE__ */ jsxs13(
5426
+ exportOpts.pdf && /* @__PURE__ */ jsxs15(
4676
5427
  "button",
4677
5428
  {
4678
5429
  type: "button",
@@ -4681,13 +5432,27 @@ function DashboardDesigner({
4681
5432
  "aria-label": t("exportPdf"),
4682
5433
  onClick: handleExportPdf,
4683
5434
  children: [
4684
- /* @__PURE__ */ jsx13(FileDown, { size: ICON_SIZE }),
5435
+ /* @__PURE__ */ jsx15(FileDown, { size: ICON_SIZE }),
4685
5436
  t("exportPdf")
4686
5437
  ]
4687
5438
  }
5439
+ ),
5440
+ exportOpts.email && /* @__PURE__ */ jsxs15(
5441
+ "button",
5442
+ {
5443
+ type: "button",
5444
+ className: "dd-btn",
5445
+ title: t("sendEmail"),
5446
+ "aria-label": t("sendEmail"),
5447
+ onClick: () => setEmailOpen(true),
5448
+ children: [
5449
+ /* @__PURE__ */ jsx15(Mail2, { size: ICON_SIZE }),
5450
+ t("emailAction")
5451
+ ]
5452
+ }
4688
5453
  )
4689
5454
  ] }),
4690
- onToggleEdit && readOnly && drillTrail.length === 0 && /* @__PURE__ */ jsxs13(
5455
+ onToggleEdit && readOnly && drillTrail.length === 0 && /* @__PURE__ */ jsxs15(
4691
5456
  "button",
4692
5457
  {
4693
5458
  type: "button",
@@ -4696,13 +5461,13 @@ function DashboardDesigner({
4696
5461
  "aria-label": t("edit"),
4697
5462
  onClick: onToggleEdit,
4698
5463
  children: [
4699
- /* @__PURE__ */ jsx13(Pencil, { size: ICON_SIZE }),
5464
+ /* @__PURE__ */ jsx15(Pencil, { size: ICON_SIZE }),
4700
5465
  t("edit")
4701
5466
  ]
4702
5467
  }
4703
5468
  ),
4704
- !effectiveReadOnly && /* @__PURE__ */ jsxs13(Fragment5, { children: [
4705
- /* @__PURE__ */ jsxs13(
5469
+ !effectiveReadOnly && /* @__PURE__ */ jsxs15(Fragment5, { children: [
5470
+ /* @__PURE__ */ jsxs15(
4706
5471
  "button",
4707
5472
  {
4708
5473
  type: "button",
@@ -4711,12 +5476,12 @@ function DashboardDesigner({
4711
5476
  "aria-label": t("shareDashboard"),
4712
5477
  onClick: () => setShareOpen(true),
4713
5478
  children: [
4714
- /* @__PURE__ */ jsx13(Share2, { size: ICON_SIZE }),
5479
+ /* @__PURE__ */ jsx15(Share2, { size: ICON_SIZE }),
4715
5480
  t("share")
4716
5481
  ]
4717
5482
  }
4718
5483
  ),
4719
- /* @__PURE__ */ jsx13(
5484
+ /* @__PURE__ */ jsx15(
4720
5485
  "button",
4721
5486
  {
4722
5487
  type: "button",
@@ -4724,10 +5489,10 @@ function DashboardDesigner({
4724
5489
  title: t("dashboardSettings"),
4725
5490
  "aria-label": t("dashboardSettings"),
4726
5491
  onClick: () => setSettingsOpen(true),
4727
- children: /* @__PURE__ */ jsx13(Settings2, { size: ICON_SIZE })
5492
+ children: /* @__PURE__ */ jsx15(Settings2, { size: ICON_SIZE })
4728
5493
  }
4729
5494
  ),
4730
- /* @__PURE__ */ jsxs13(
5495
+ /* @__PURE__ */ jsxs15(
4731
5496
  "button",
4732
5497
  {
4733
5498
  type: "button",
@@ -4736,13 +5501,13 @@ function DashboardDesigner({
4736
5501
  "aria-label": t("versionHistory"),
4737
5502
  onClick: () => setHistoryOpen(true),
4738
5503
  children: [
4739
- /* @__PURE__ */ jsx13(History2, { size: ICON_SIZE }),
5504
+ /* @__PURE__ */ jsx15(History2, { size: ICON_SIZE }),
4740
5505
  t("history")
4741
5506
  ]
4742
5507
  }
4743
5508
  ),
4744
- /* @__PURE__ */ jsxs13("div", { className: "dd-toolbar-history", role: "group", "aria-label": t("editHistory"), children: [
4745
- /* @__PURE__ */ jsx13(
5509
+ /* @__PURE__ */ jsxs15("div", { className: "dd-toolbar-history", role: "group", "aria-label": t("editHistory"), children: [
5510
+ /* @__PURE__ */ jsx15(
4746
5511
  "button",
4747
5512
  {
4748
5513
  type: "button",
@@ -4751,10 +5516,10 @@ function DashboardDesigner({
4751
5516
  "aria-label": t("undo"),
4752
5517
  disabled: !canUndo,
4753
5518
  onClick: handleUndo,
4754
- children: /* @__PURE__ */ jsx13(Undo2, { size: ICON_SIZE })
5519
+ children: /* @__PURE__ */ jsx15(Undo2, { size: ICON_SIZE })
4755
5520
  }
4756
5521
  ),
4757
- /* @__PURE__ */ jsx13(
5522
+ /* @__PURE__ */ jsx15(
4758
5523
  "button",
4759
5524
  {
4760
5525
  type: "button",
@@ -4763,10 +5528,10 @@ function DashboardDesigner({
4763
5528
  "aria-label": t("redo"),
4764
5529
  disabled: !canRedo,
4765
5530
  onClick: handleRedo,
4766
- children: /* @__PURE__ */ jsx13(Redo2, { size: ICON_SIZE })
5531
+ children: /* @__PURE__ */ jsx15(Redo2, { size: ICON_SIZE })
4767
5532
  }
4768
5533
  ),
4769
- /* @__PURE__ */ jsx13(
5534
+ /* @__PURE__ */ jsx15(
4770
5535
  "button",
4771
5536
  {
4772
5537
  type: "button",
@@ -4775,23 +5540,23 @@ function DashboardDesigner({
4775
5540
  "aria-label": t("reset"),
4776
5541
  disabled: !canReset,
4777
5542
  onClick: handleReset,
4778
- children: /* @__PURE__ */ jsx13(RotateCcw3, { size: ICON_SIZE })
5543
+ children: /* @__PURE__ */ jsx15(RotateCcw3, { size: ICON_SIZE })
4779
5544
  }
4780
5545
  )
4781
5546
  ] }),
4782
- /* @__PURE__ */ jsxs13("button", { type: "button", className: "dd-btn", onClick: openAddModal, children: [
4783
- /* @__PURE__ */ jsx13(Plus5, { size: ICON_SIZE }),
5547
+ /* @__PURE__ */ jsxs15("button", { type: "button", className: "dd-btn", onClick: openAddModal, children: [
5548
+ /* @__PURE__ */ jsx15(Plus6, { size: ICON_SIZE }),
4784
5549
  t("add")
4785
5550
  ] }),
4786
- /* @__PURE__ */ jsxs13("button", { type: "button", className: "dd-btn dd-btn-primary", onClick: () => void handleSave(), children: [
4787
- /* @__PURE__ */ jsx13(Save2, { size: ICON_SIZE }),
5551
+ /* @__PURE__ */ jsxs15("button", { type: "button", className: "dd-btn dd-btn-primary", onClick: () => void handleSave(), children: [
5552
+ /* @__PURE__ */ jsx15(Save2, { size: ICON_SIZE }),
4788
5553
  t("save")
4789
5554
  ] })
4790
5555
  ] })
4791
5556
  ] })
4792
5557
  ] }),
4793
- /* @__PURE__ */ jsxs13("div", { className: "dd-workspace", children: [
4794
- /* @__PURE__ */ jsx13(
5558
+ /* @__PURE__ */ jsxs15("div", { className: "dd-workspace", children: [
5559
+ /* @__PURE__ */ jsx15(
4795
5560
  GlobalFiltersSidePanel,
4796
5561
  {
4797
5562
  filters: dashboard.globalFilters,
@@ -4801,15 +5566,15 @@ function DashboardDesigner({
4801
5566
  onPanelChange: setFiltersPanel
4802
5567
  }
4803
5568
  ),
4804
- /* @__PURE__ */ jsx13("div", { className: "dd-main dd-main-canvas-only", children: /* @__PURE__ */ jsxs13("div", { id: "dd-canvas", className: "dd-canvas", children: [
4805
- dashboard.widgets.length === 0 && !effectiveReadOnly && /* @__PURE__ */ jsxs13("div", { className: "dd-canvas-empty", children: [
4806
- /* @__PURE__ */ jsx13("p", { children: t("emptyCanvas") }),
4807
- /* @__PURE__ */ jsxs13("button", { type: "button", className: "dd-btn dd-btn-primary", onClick: openAddModal, children: [
4808
- /* @__PURE__ */ jsx13(Plus5, { size: ICON_SIZE }),
5569
+ /* @__PURE__ */ jsx15("div", { className: "dd-main dd-main-canvas-only", children: /* @__PURE__ */ jsxs15("div", { id: "dd-canvas", className: "dd-canvas", children: [
5570
+ dashboard.widgets.length === 0 && !effectiveReadOnly && /* @__PURE__ */ jsxs15("div", { className: "dd-canvas-empty", children: [
5571
+ /* @__PURE__ */ jsx15("p", { children: t("emptyCanvas") }),
5572
+ /* @__PURE__ */ jsxs15("button", { type: "button", className: "dd-btn dd-btn-primary", onClick: openAddModal, children: [
5573
+ /* @__PURE__ */ jsx15(Plus6, { size: ICON_SIZE }),
4809
5574
  t("addWidget")
4810
5575
  ] })
4811
5576
  ] }),
4812
- /* @__PURE__ */ jsx13(
5577
+ /* @__PURE__ */ jsx15(
4813
5578
  GridLayout,
4814
5579
  {
4815
5580
  className: "layout",
@@ -4829,7 +5594,7 @@ function DashboardDesigner({
4829
5594
  draggableHandle: ".dd-widget-drag",
4830
5595
  draggableCancel: ".dd-widget-edit, .dd-widget-delete, .dd-widget-header-actions, .dd-chart-fill, .recharts-wrapper, .dd-listbox",
4831
5596
  compactType: "vertical",
4832
- children: dashboard.widgets.map((widget) => /* @__PURE__ */ jsx13("div", { children: /* @__PURE__ */ jsx13(
5597
+ children: dashboard.widgets.map((widget) => /* @__PURE__ */ jsx15("div", { children: /* @__PURE__ */ jsx15(
4833
5598
  WidgetCard,
4834
5599
  {
4835
5600
  widget,
@@ -4857,7 +5622,7 @@ function DashboardDesigner({
4857
5622
  )
4858
5623
  ] }) })
4859
5624
  ] }),
4860
- !effectiveReadOnly && /* @__PURE__ */ jsx13(
5625
+ !effectiveReadOnly && /* @__PURE__ */ jsx15(
4861
5626
  WidgetEditorModal,
4862
5627
  {
4863
5628
  open: modalOpen,
@@ -4875,7 +5640,7 @@ function DashboardDesigner({
4875
5640
  }
4876
5641
  }
4877
5642
  ),
4878
- /* @__PURE__ */ jsx13(
5643
+ /* @__PURE__ */ jsx15(
4879
5644
  HistoryPanel,
4880
5645
  {
4881
5646
  open: historyOpen && !effectiveReadOnly,
@@ -4892,7 +5657,7 @@ function DashboardDesigner({
4892
5657
  }
4893
5658
  }
4894
5659
  ),
4895
- /* @__PURE__ */ jsx13(
5660
+ /* @__PURE__ */ jsx15(
4896
5661
  SharePanel,
4897
5662
  {
4898
5663
  open: shareOpen && !effectiveReadOnly,
@@ -4901,7 +5666,16 @@ function DashboardDesigner({
4901
5666
  api
4902
5667
  }
4903
5668
  ),
4904
- /* @__PURE__ */ jsx13(
5669
+ /* @__PURE__ */ jsx15(
5670
+ EmailComposePanel,
5671
+ {
5672
+ open: emailOpen,
5673
+ onClose: () => setEmailOpen(false),
5674
+ dashboard,
5675
+ api
5676
+ }
5677
+ ),
5678
+ /* @__PURE__ */ jsx15(
4905
5679
  DashboardSettingsPanel,
4906
5680
  {
4907
5681
  open: settingsOpen && !effectiveReadOnly,
@@ -4916,9 +5690,9 @@ function DashboardDesigner({
4916
5690
  }
4917
5691
 
4918
5692
  // src/viewer/DashboardViewer.tsx
4919
- import { jsx as jsx14 } from "react/jsx-runtime";
5693
+ import { jsx as jsx16 } from "react/jsx-runtime";
4920
5694
  function DashboardViewer({ dashboard, apiBaseUrl, auth, locale }) {
4921
- return /* @__PURE__ */ jsx14(
5695
+ return /* @__PURE__ */ jsx16(
4922
5696
  DashboardDesigner,
4923
5697
  {
4924
5698
  dashboard,