@dashboard-designer/ui 0.1.0 → 0.1.2

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(() => {
@@ -4131,9 +4881,7 @@ function useDashboardHistory(initial, onChange, readOnly = false) {
4131
4881
  }
4132
4882
 
4133
4883
  // src/designer/DashboardDesigner.tsx
4134
- import "react-grid-layout/css/styles.css";
4135
- import "react-resizable/css/styles.css";
4136
- import { Fragment as Fragment5, jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
4884
+ import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
4137
4885
  function listboxFilterField(widget) {
4138
4886
  const override = widget.chartOptions.filterField;
4139
4887
  if (typeof override === "string" && override.trim()) return override.trim();
@@ -4189,25 +4937,25 @@ function WidgetCard({
4189
4937
  listboxSelection,
4190
4938
  onListboxSelectionChange
4191
4939
  }) {
4192
- const { t } = useTranslation13();
4940
+ const { t } = useTranslation15();
4193
4941
  const { data, error, loading } = useWidgetData(api, widget, globalFilters, refreshKey);
4194
4942
  const TypeIcon = chartTypeIcon[widget.type];
4195
4943
  const stopDrag = (e) => {
4196
4944
  e.stopPropagation();
4197
4945
  };
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 })
4946
+ return /* @__PURE__ */ jsxs15("div", { className: `dd-widget ${selected ? "selected" : ""}`, onClick: onSelect, role: "presentation", children: [
4947
+ /* @__PURE__ */ jsxs15("div", { className: "dd-widget-header", children: [
4948
+ /* @__PURE__ */ jsxs15("div", { className: readOnly ? "dd-widget-title-wrap" : "dd-widget-drag", children: [
4949
+ !readOnly && /* @__PURE__ */ jsx15("span", { className: "dd-widget-drag-grip", "aria-hidden": true }),
4950
+ /* @__PURE__ */ jsx15("span", { className: "dd-widget-title", children: widget.title })
4203
4951
  ] }),
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 }),
4952
+ /* @__PURE__ */ jsxs15("div", { className: "dd-widget-header-actions", children: [
4953
+ /* @__PURE__ */ jsxs15("span", { className: "dd-badge", title: widget.type, children: [
4954
+ /* @__PURE__ */ jsx15(TypeIcon, { size: 12, strokeWidth: 2.25, "aria-hidden": true }),
4207
4955
  widget.type
4208
4956
  ] }),
4209
- !readOnly && /* @__PURE__ */ jsxs13(Fragment5, { children: [
4210
- /* @__PURE__ */ jsx13(
4957
+ !readOnly && /* @__PURE__ */ jsxs15(Fragment5, { children: [
4958
+ /* @__PURE__ */ jsx15(
4211
4959
  "button",
4212
4960
  {
4213
4961
  type: "button",
@@ -4221,10 +4969,10 @@ function WidgetCard({
4221
4969
  e.stopPropagation();
4222
4970
  onEdit();
4223
4971
  },
4224
- children: /* @__PURE__ */ jsx13(Pencil, { size: ICON_SIZE })
4972
+ children: /* @__PURE__ */ jsx15(Pencil, { size: ICON_SIZE })
4225
4973
  }
4226
4974
  ),
4227
- /* @__PURE__ */ jsx13(
4975
+ /* @__PURE__ */ jsx15(
4228
4976
  "button",
4229
4977
  {
4230
4978
  type: "button",
@@ -4238,13 +4986,13 @@ function WidgetCard({
4238
4986
  e.stopPropagation();
4239
4987
  onDelete();
4240
4988
  },
4241
- children: /* @__PURE__ */ jsx13(Trash24, { size: ICON_SIZE })
4989
+ children: /* @__PURE__ */ jsx15(Trash25, { size: ICON_SIZE })
4242
4990
  }
4243
4991
  )
4244
4992
  ] })
4245
4993
  ] })
4246
4994
  ] }),
4247
- /* @__PURE__ */ jsxs13(
4995
+ /* @__PURE__ */ jsxs15(
4248
4996
  "div",
4249
4997
  {
4250
4998
  className: "dd-widget-body",
@@ -4254,8 +5002,8 @@ function WidgetCard({
4254
5002
  onEdit();
4255
5003
  },
4256
5004
  children: [
4257
- error && /* @__PURE__ */ jsx13("div", { className: "dd-error", children: error }),
4258
- !error && /* @__PURE__ */ jsx13(
5005
+ error && /* @__PURE__ */ jsx15("div", { className: "dd-error", children: error }),
5006
+ !error && /* @__PURE__ */ jsx15(
4259
5007
  ChartRenderer,
4260
5008
  {
4261
5009
  widget,
@@ -4286,7 +5034,7 @@ function DashboardDesigner({
4286
5034
  onToggleEdit
4287
5035
  }) {
4288
5036
  ensureI18n(locale);
4289
- const { t, i18n: i18n2 } = useTranslation13();
5037
+ const { t, i18n: i18n2 } = useTranslation15();
4290
5038
  const api = useMemo6(() => new DashboardApiClient(apiBaseUrl, auth), [apiBaseUrl, auth]);
4291
5039
  const seed = useMemo6(() => initial ?? createEmptyDashboard(), [initial]);
4292
5040
  const {
@@ -4306,19 +5054,19 @@ function DashboardDesigner({
4306
5054
  canRedo,
4307
5055
  canReset
4308
5056
  } = 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(() => {
5057
+ const [editingTitle, setEditingTitle] = useState12(false);
5058
+ const [historyOpen, setHistoryOpen] = useState12(false);
5059
+ const [filtersPanel, setFiltersPanel] = useState12(DEFAULT_FILTERS_PANEL);
5060
+ const titleInputRef = React13.useRef(null);
5061
+ const titleFocusSnapshot = React13.useRef(null);
5062
+ useEffect13(() => {
4315
5063
  if (!editingTitle) return;
4316
5064
  const el = titleInputRef.current;
4317
5065
  if (!el) return;
4318
5066
  el.focus();
4319
5067
  el.select();
4320
5068
  }, [editingTitle]);
4321
- useEffect11(() => {
5069
+ useEffect13(() => {
4322
5070
  if (readOnly) {
4323
5071
  setEditingTitle(false);
4324
5072
  setHistoryOpen(false);
@@ -4326,18 +5074,19 @@ function DashboardDesigner({
4326
5074
  setSettingsOpen(false);
4327
5075
  }
4328
5076
  }, [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({});
5077
+ const [selectedId, setSelectedId] = useState12(null);
5078
+ const [connections, setConnections] = useState12([]);
5079
+ const [refreshKey, setRefreshKey] = useState12(0);
5080
+ const [width, setWidth] = useState12(900);
5081
+ const [drillTrail, setDrillTrail] = useState12([]);
5082
+ const [modalOpen, setModalOpen] = useState12(false);
5083
+ const [modalMode, setModalMode] = useState12("add");
5084
+ const [editingWidget, setEditingWidget] = useState12(null);
5085
+ const [shareOpen, setShareOpen] = useState12(false);
5086
+ const [emailOpen, setEmailOpen] = useState12(false);
5087
+ const [settingsOpen, setSettingsOpen] = useState12(false);
5088
+ const [drillError, setDrillError] = useState12(null);
5089
+ const [listboxSelections, setListboxSelections] = useState12({});
4341
5090
  const effectiveReadOnly = readOnly || drillTrail.length > 0;
4342
5091
  const textDir = resolveDashboardDir(dashboard);
4343
5092
  const effectiveLocale = dashboard.locale || locale;
@@ -4350,7 +5099,7 @@ function DashboardDesigner({
4350
5099
  const handleExportPdf = () => {
4351
5100
  exportDashboardPdf(document.getElementById("dd-canvas"), dashboard.title);
4352
5101
  };
4353
- useEffect11(() => {
5102
+ useEffect13(() => {
4354
5103
  setListboxSelections({});
4355
5104
  }, [dashboard.id]);
4356
5105
  const effectiveFiltersFor = (widget) => {
@@ -4362,13 +5111,13 @@ function DashboardDesigner({
4362
5111
  const globals = dashboard.globalFilters.filter((f) => !f.id.startsWith("listbox:"));
4363
5112
  return [...globals, ...fromListboxes];
4364
5113
  };
4365
- useEffect11(() => {
5114
+ useEffect13(() => {
4366
5115
  void i18n2.changeLanguage(effectiveLocale);
4367
5116
  }, [effectiveLocale, i18n2]);
4368
- useEffect11(() => {
5117
+ useEffect13(() => {
4369
5118
  api.listConnections().then((r) => setConnections(r.connections)).catch(() => setConnections([]));
4370
5119
  }, [api]);
4371
- useEffect11(() => {
5120
+ useEffect13(() => {
4372
5121
  const el = document.getElementById("dd-canvas");
4373
5122
  if (!el) return;
4374
5123
  const measure = () => {
@@ -4381,7 +5130,7 @@ function DashboardDesigner({
4381
5130
  measure();
4382
5131
  return () => ro.disconnect();
4383
5132
  }, []);
4384
- useEffect11(() => {
5133
+ useEffect13(() => {
4385
5134
  if (effectiveReadOnly) return;
4386
5135
  const onKeyDown = (e) => {
4387
5136
  const target = e.target;
@@ -4582,16 +5331,16 @@ function DashboardDesigner({
4582
5331
  }
4583
5332
  setRefreshKey((k) => k + 1);
4584
5333
  };
4585
- return /* @__PURE__ */ jsxs13(
5334
+ return /* @__PURE__ */ jsxs15(
4586
5335
  "div",
4587
5336
  {
4588
5337
  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
5338
  dir: textDir,
4590
5339
  style: themeStyle,
4591
5340
  children: [
4592
- /* @__PURE__ */ jsxs13("header", { className: "dd-toolbar", children: [
4593
- /* @__PURE__ */ jsxs13("div", { className: "dd-toolbar-left", children: [
4594
- editingTitle && !effectiveReadOnly ? /* @__PURE__ */ jsx13(
5341
+ /* @__PURE__ */ jsxs15("header", { className: "dd-toolbar", children: [
5342
+ /* @__PURE__ */ jsxs15("div", { className: "dd-toolbar-left", children: [
5343
+ editingTitle && !effectiveReadOnly ? /* @__PURE__ */ jsx15(
4595
5344
  "input",
4596
5345
  {
4597
5346
  ref: titleInputRef,
@@ -4621,7 +5370,7 @@ function DashboardDesigner({
4621
5370
  }
4622
5371
  }
4623
5372
  }
4624
- ) : /* @__PURE__ */ jsx13(
5373
+ ) : /* @__PURE__ */ jsx15(
4625
5374
  "button",
4626
5375
  {
4627
5376
  type: "button",
@@ -4635,19 +5384,19 @@ function DashboardDesigner({
4635
5384
  children: dashboard.title || t("title")
4636
5385
  }
4637
5386
  ),
4638
- drillTrail.length > 0 && /* @__PURE__ */ jsxs13("span", { className: "dd-drill-crumb dd-muted", children: [
5387
+ drillTrail.length > 0 && /* @__PURE__ */ jsxs15("span", { className: "dd-drill-crumb dd-muted", children: [
4639
5388
  drillTrail.map((f) => f.title).join(" \u203A "),
4640
5389
  " \u203A ",
4641
5390
  dashboard.title
4642
5391
  ] })
4643
5392
  ] }),
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 }),
5393
+ /* @__PURE__ */ jsxs15("div", { className: "dd-toolbar-right", children: [
5394
+ drillError && /* @__PURE__ */ jsx15("span", { className: "dd-error dd-inline-error", children: drillError }),
5395
+ drillTrail.length > 0 && /* @__PURE__ */ jsxs15("button", { type: "button", className: "dd-btn", onClick: handleDrillBack, children: [
5396
+ /* @__PURE__ */ jsx15(ArrowUpLeft, { size: ICON_SIZE }),
4648
5397
  t("drillBack")
4649
5398
  ] }),
4650
- /* @__PURE__ */ jsx13(
5399
+ /* @__PURE__ */ jsx15(
4651
5400
  GlobalFiltersPopover,
4652
5401
  {
4653
5402
  filters: dashboard.globalFilters,
@@ -4657,8 +5406,8 @@ function DashboardDesigner({
4657
5406
  onPanelChange: setFiltersPanel
4658
5407
  }
4659
5408
  ),
4660
- (exportOpts.print || exportOpts.pdf) && /* @__PURE__ */ jsxs13("div", { className: "dd-toolbar-export", role: "group", "aria-label": t("exportOptions"), children: [
4661
- exportOpts.print && /* @__PURE__ */ jsxs13(
5409
+ (exportOpts.print || exportOpts.pdf || exportOpts.email) && /* @__PURE__ */ jsxs15("div", { className: "dd-toolbar-export", role: "group", "aria-label": t("exportOptions"), children: [
5410
+ exportOpts.print && /* @__PURE__ */ jsxs15(
4662
5411
  "button",
4663
5412
  {
4664
5413
  type: "button",
@@ -4667,12 +5416,12 @@ function DashboardDesigner({
4667
5416
  "aria-label": t("printDashboard"),
4668
5417
  onClick: handlePrint,
4669
5418
  children: [
4670
- /* @__PURE__ */ jsx13(Printer, { size: ICON_SIZE }),
5419
+ /* @__PURE__ */ jsx15(Printer, { size: ICON_SIZE }),
4671
5420
  t("print")
4672
5421
  ]
4673
5422
  }
4674
5423
  ),
4675
- exportOpts.pdf && /* @__PURE__ */ jsxs13(
5424
+ exportOpts.pdf && /* @__PURE__ */ jsxs15(
4676
5425
  "button",
4677
5426
  {
4678
5427
  type: "button",
@@ -4681,13 +5430,27 @@ function DashboardDesigner({
4681
5430
  "aria-label": t("exportPdf"),
4682
5431
  onClick: handleExportPdf,
4683
5432
  children: [
4684
- /* @__PURE__ */ jsx13(FileDown, { size: ICON_SIZE }),
5433
+ /* @__PURE__ */ jsx15(FileDown, { size: ICON_SIZE }),
4685
5434
  t("exportPdf")
4686
5435
  ]
4687
5436
  }
5437
+ ),
5438
+ exportOpts.email && /* @__PURE__ */ jsxs15(
5439
+ "button",
5440
+ {
5441
+ type: "button",
5442
+ className: "dd-btn",
5443
+ title: t("sendEmail"),
5444
+ "aria-label": t("sendEmail"),
5445
+ onClick: () => setEmailOpen(true),
5446
+ children: [
5447
+ /* @__PURE__ */ jsx15(Mail2, { size: ICON_SIZE }),
5448
+ t("emailAction")
5449
+ ]
5450
+ }
4688
5451
  )
4689
5452
  ] }),
4690
- onToggleEdit && readOnly && drillTrail.length === 0 && /* @__PURE__ */ jsxs13(
5453
+ onToggleEdit && readOnly && drillTrail.length === 0 && /* @__PURE__ */ jsxs15(
4691
5454
  "button",
4692
5455
  {
4693
5456
  type: "button",
@@ -4696,13 +5459,13 @@ function DashboardDesigner({
4696
5459
  "aria-label": t("edit"),
4697
5460
  onClick: onToggleEdit,
4698
5461
  children: [
4699
- /* @__PURE__ */ jsx13(Pencil, { size: ICON_SIZE }),
5462
+ /* @__PURE__ */ jsx15(Pencil, { size: ICON_SIZE }),
4700
5463
  t("edit")
4701
5464
  ]
4702
5465
  }
4703
5466
  ),
4704
- !effectiveReadOnly && /* @__PURE__ */ jsxs13(Fragment5, { children: [
4705
- /* @__PURE__ */ jsxs13(
5467
+ !effectiveReadOnly && /* @__PURE__ */ jsxs15(Fragment5, { children: [
5468
+ /* @__PURE__ */ jsxs15(
4706
5469
  "button",
4707
5470
  {
4708
5471
  type: "button",
@@ -4711,12 +5474,12 @@ function DashboardDesigner({
4711
5474
  "aria-label": t("shareDashboard"),
4712
5475
  onClick: () => setShareOpen(true),
4713
5476
  children: [
4714
- /* @__PURE__ */ jsx13(Share2, { size: ICON_SIZE }),
5477
+ /* @__PURE__ */ jsx15(Share2, { size: ICON_SIZE }),
4715
5478
  t("share")
4716
5479
  ]
4717
5480
  }
4718
5481
  ),
4719
- /* @__PURE__ */ jsx13(
5482
+ /* @__PURE__ */ jsx15(
4720
5483
  "button",
4721
5484
  {
4722
5485
  type: "button",
@@ -4724,10 +5487,10 @@ function DashboardDesigner({
4724
5487
  title: t("dashboardSettings"),
4725
5488
  "aria-label": t("dashboardSettings"),
4726
5489
  onClick: () => setSettingsOpen(true),
4727
- children: /* @__PURE__ */ jsx13(Settings2, { size: ICON_SIZE })
5490
+ children: /* @__PURE__ */ jsx15(Settings2, { size: ICON_SIZE })
4728
5491
  }
4729
5492
  ),
4730
- /* @__PURE__ */ jsxs13(
5493
+ /* @__PURE__ */ jsxs15(
4731
5494
  "button",
4732
5495
  {
4733
5496
  type: "button",
@@ -4736,13 +5499,13 @@ function DashboardDesigner({
4736
5499
  "aria-label": t("versionHistory"),
4737
5500
  onClick: () => setHistoryOpen(true),
4738
5501
  children: [
4739
- /* @__PURE__ */ jsx13(History2, { size: ICON_SIZE }),
5502
+ /* @__PURE__ */ jsx15(History2, { size: ICON_SIZE }),
4740
5503
  t("history")
4741
5504
  ]
4742
5505
  }
4743
5506
  ),
4744
- /* @__PURE__ */ jsxs13("div", { className: "dd-toolbar-history", role: "group", "aria-label": t("editHistory"), children: [
4745
- /* @__PURE__ */ jsx13(
5507
+ /* @__PURE__ */ jsxs15("div", { className: "dd-toolbar-history", role: "group", "aria-label": t("editHistory"), children: [
5508
+ /* @__PURE__ */ jsx15(
4746
5509
  "button",
4747
5510
  {
4748
5511
  type: "button",
@@ -4751,10 +5514,10 @@ function DashboardDesigner({
4751
5514
  "aria-label": t("undo"),
4752
5515
  disabled: !canUndo,
4753
5516
  onClick: handleUndo,
4754
- children: /* @__PURE__ */ jsx13(Undo2, { size: ICON_SIZE })
5517
+ children: /* @__PURE__ */ jsx15(Undo2, { size: ICON_SIZE })
4755
5518
  }
4756
5519
  ),
4757
- /* @__PURE__ */ jsx13(
5520
+ /* @__PURE__ */ jsx15(
4758
5521
  "button",
4759
5522
  {
4760
5523
  type: "button",
@@ -4763,10 +5526,10 @@ function DashboardDesigner({
4763
5526
  "aria-label": t("redo"),
4764
5527
  disabled: !canRedo,
4765
5528
  onClick: handleRedo,
4766
- children: /* @__PURE__ */ jsx13(Redo2, { size: ICON_SIZE })
5529
+ children: /* @__PURE__ */ jsx15(Redo2, { size: ICON_SIZE })
4767
5530
  }
4768
5531
  ),
4769
- /* @__PURE__ */ jsx13(
5532
+ /* @__PURE__ */ jsx15(
4770
5533
  "button",
4771
5534
  {
4772
5535
  type: "button",
@@ -4775,23 +5538,23 @@ function DashboardDesigner({
4775
5538
  "aria-label": t("reset"),
4776
5539
  disabled: !canReset,
4777
5540
  onClick: handleReset,
4778
- children: /* @__PURE__ */ jsx13(RotateCcw3, { size: ICON_SIZE })
5541
+ children: /* @__PURE__ */ jsx15(RotateCcw3, { size: ICON_SIZE })
4779
5542
  }
4780
5543
  )
4781
5544
  ] }),
4782
- /* @__PURE__ */ jsxs13("button", { type: "button", className: "dd-btn", onClick: openAddModal, children: [
4783
- /* @__PURE__ */ jsx13(Plus5, { size: ICON_SIZE }),
5545
+ /* @__PURE__ */ jsxs15("button", { type: "button", className: "dd-btn", onClick: openAddModal, children: [
5546
+ /* @__PURE__ */ jsx15(Plus6, { size: ICON_SIZE }),
4784
5547
  t("add")
4785
5548
  ] }),
4786
- /* @__PURE__ */ jsxs13("button", { type: "button", className: "dd-btn dd-btn-primary", onClick: () => void handleSave(), children: [
4787
- /* @__PURE__ */ jsx13(Save2, { size: ICON_SIZE }),
5549
+ /* @__PURE__ */ jsxs15("button", { type: "button", className: "dd-btn dd-btn-primary", onClick: () => void handleSave(), children: [
5550
+ /* @__PURE__ */ jsx15(Save2, { size: ICON_SIZE }),
4788
5551
  t("save")
4789
5552
  ] })
4790
5553
  ] })
4791
5554
  ] })
4792
5555
  ] }),
4793
- /* @__PURE__ */ jsxs13("div", { className: "dd-workspace", children: [
4794
- /* @__PURE__ */ jsx13(
5556
+ /* @__PURE__ */ jsxs15("div", { className: "dd-workspace", children: [
5557
+ /* @__PURE__ */ jsx15(
4795
5558
  GlobalFiltersSidePanel,
4796
5559
  {
4797
5560
  filters: dashboard.globalFilters,
@@ -4801,15 +5564,15 @@ function DashboardDesigner({
4801
5564
  onPanelChange: setFiltersPanel
4802
5565
  }
4803
5566
  ),
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 }),
5567
+ /* @__PURE__ */ jsx15("div", { className: "dd-main dd-main-canvas-only", children: /* @__PURE__ */ jsxs15("div", { id: "dd-canvas", className: "dd-canvas", children: [
5568
+ dashboard.widgets.length === 0 && !effectiveReadOnly && /* @__PURE__ */ jsxs15("div", { className: "dd-canvas-empty", children: [
5569
+ /* @__PURE__ */ jsx15("p", { children: t("emptyCanvas") }),
5570
+ /* @__PURE__ */ jsxs15("button", { type: "button", className: "dd-btn dd-btn-primary", onClick: openAddModal, children: [
5571
+ /* @__PURE__ */ jsx15(Plus6, { size: ICON_SIZE }),
4809
5572
  t("addWidget")
4810
5573
  ] })
4811
5574
  ] }),
4812
- /* @__PURE__ */ jsx13(
5575
+ /* @__PURE__ */ jsx15(
4813
5576
  GridLayout,
4814
5577
  {
4815
5578
  className: "layout",
@@ -4829,7 +5592,7 @@ function DashboardDesigner({
4829
5592
  draggableHandle: ".dd-widget-drag",
4830
5593
  draggableCancel: ".dd-widget-edit, .dd-widget-delete, .dd-widget-header-actions, .dd-chart-fill, .recharts-wrapper, .dd-listbox",
4831
5594
  compactType: "vertical",
4832
- children: dashboard.widgets.map((widget) => /* @__PURE__ */ jsx13("div", { children: /* @__PURE__ */ jsx13(
5595
+ children: dashboard.widgets.map((widget) => /* @__PURE__ */ jsx15("div", { children: /* @__PURE__ */ jsx15(
4833
5596
  WidgetCard,
4834
5597
  {
4835
5598
  widget,
@@ -4857,7 +5620,7 @@ function DashboardDesigner({
4857
5620
  )
4858
5621
  ] }) })
4859
5622
  ] }),
4860
- !effectiveReadOnly && /* @__PURE__ */ jsx13(
5623
+ !effectiveReadOnly && /* @__PURE__ */ jsx15(
4861
5624
  WidgetEditorModal,
4862
5625
  {
4863
5626
  open: modalOpen,
@@ -4875,7 +5638,7 @@ function DashboardDesigner({
4875
5638
  }
4876
5639
  }
4877
5640
  ),
4878
- /* @__PURE__ */ jsx13(
5641
+ /* @__PURE__ */ jsx15(
4879
5642
  HistoryPanel,
4880
5643
  {
4881
5644
  open: historyOpen && !effectiveReadOnly,
@@ -4892,7 +5655,7 @@ function DashboardDesigner({
4892
5655
  }
4893
5656
  }
4894
5657
  ),
4895
- /* @__PURE__ */ jsx13(
5658
+ /* @__PURE__ */ jsx15(
4896
5659
  SharePanel,
4897
5660
  {
4898
5661
  open: shareOpen && !effectiveReadOnly,
@@ -4901,7 +5664,16 @@ function DashboardDesigner({
4901
5664
  api
4902
5665
  }
4903
5666
  ),
4904
- /* @__PURE__ */ jsx13(
5667
+ /* @__PURE__ */ jsx15(
5668
+ EmailComposePanel,
5669
+ {
5670
+ open: emailOpen,
5671
+ onClose: () => setEmailOpen(false),
5672
+ dashboard,
5673
+ api
5674
+ }
5675
+ ),
5676
+ /* @__PURE__ */ jsx15(
4905
5677
  DashboardSettingsPanel,
4906
5678
  {
4907
5679
  open: settingsOpen && !effectiveReadOnly,
@@ -4916,9 +5688,9 @@ function DashboardDesigner({
4916
5688
  }
4917
5689
 
4918
5690
  // src/viewer/DashboardViewer.tsx
4919
- import { jsx as jsx14 } from "react/jsx-runtime";
5691
+ import { jsx as jsx16 } from "react/jsx-runtime";
4920
5692
  function DashboardViewer({ dashboard, apiBaseUrl, auth, locale }) {
4921
- return /* @__PURE__ */ jsx14(
5693
+ return /* @__PURE__ */ jsx16(
4922
5694
  DashboardDesigner,
4923
5695
  {
4924
5696
  dashboard,