@nextelco/common-ui 2.1.105 → 2.1.107
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/esm/index.js +201 -145
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/legacy/components/atoms/InputData/InputData.js +2 -13
- package/dist/esm/legacy/components/atoms/InputData/InputData.js.map +1 -1
- package/dist/esm/legacy/components/charts/Charts.js +147 -57
- package/dist/esm/legacy/components/charts/Charts.js.map +1 -1
- package/dist/esm/legacy/components/layout/Layout/Layout.js +21 -82
- package/dist/esm/legacy/components/layout/Layout/Layout.js.map +1 -1
- package/dist/esm/legacy/components/layout/Sidebar/Sidebar.js +29 -218
- package/dist/esm/legacy/components/layout/Sidebar/Sidebar.js.map +1 -1
- package/dist/esm/legacy/components/molecules/ColumnPicker/ColumnPicker.js +77 -10
- package/dist/esm/legacy/components/molecules/ColumnPicker/ColumnPicker.js.map +1 -1
- package/dist/esm/legacy/components/molecules/DataTable/DataTable.js +445 -337
- package/dist/esm/legacy/components/molecules/DataTable/DataTable.js.map +1 -1
- package/dist/esm/legacy/components/molecules/UserAvatar/UserAvatar.js +15 -97
- package/dist/esm/legacy/components/molecules/UserAvatar/UserAvatar.js.map +1 -1
- package/dist/esm/legacy/components/organisms/MapaPontos/MapaPontos.js +3 -3
- package/dist/esm/legacy/components/organisms/MapaPontos/MapaPontos.js.map +1 -1
- package/dist/esm/legacy/components/organisms/PainelFiltros/PainelFiltros.js +15 -3
- package/dist/esm/legacy/components/organisms/PainelFiltros/PainelFiltros.js.map +1 -1
- package/dist/esm/legacy/components/organisms/Selector/Selector.js +4 -4
- package/dist/esm/legacy/components/organisms/Selector/Selector.js.map +1 -1
- package/dist/esm/legacy/components/ui/Avisos.js +23 -76
- package/dist/esm/legacy/components/ui/Avisos.js.map +1 -1
- package/dist/esm/legacy/components/ui/CabecalhoPagina.js +14 -27
- package/dist/esm/legacy/components/ui/CabecalhoPagina.js.map +1 -1
- package/dist/esm/legacy/components/ui/CaixaPesquisa.js +3 -51
- package/dist/esm/legacy/components/ui/CaixaPesquisa.js.map +1 -1
- package/dist/esm/legacy/components/ui/Confirmacao.js +13 -31
- package/dist/esm/legacy/components/ui/Confirmacao.js.map +1 -1
- package/dist/esm/legacy/components/ui/Escolha.js +21 -137
- package/dist/esm/legacy/components/ui/Escolha.js.map +1 -1
- package/dist/esm/legacy/components/ui/FaixaAviso.js +13 -11
- package/dist/esm/legacy/components/ui/FaixaAviso.js.map +1 -1
- package/dist/esm/legacy/components/ui/Separadores.js +11 -46
- package/dist/esm/legacy/components/ui/Separadores.js.map +1 -1
- package/dist/esm/legacy/components/ui/Ui.js +18 -115
- package/dist/esm/legacy/components/ui/Ui.js.map +1 -1
- package/dist/esm/legacy/components/ui/painelFlutuante.js +5 -44
- package/dist/esm/legacy/components/ui/painelFlutuante.js.map +1 -1
- package/dist/esm/v1/capabilities/overlay/useFloatingPanel.js +52 -0
- package/dist/esm/v1/capabilities/overlay/useFloatingPanel.js.map +1 -0
- package/dist/esm/v1/capabilities/theme/useIsDark.js +15 -0
- package/dist/esm/v1/capabilities/theme/useIsDark.js.map +1 -0
- package/dist/esm/v1/capabilities/theme/useTheme.js +66 -0
- package/dist/esm/v1/capabilities/theme/useTheme.js.map +1 -0
- package/dist/esm/v1/components/data/Pagination/Pagination.js +66 -0
- package/dist/esm/v1/components/data/Pagination/Pagination.js.map +1 -0
- package/dist/esm/v1/components/feedback/Toast/Toast.js +105 -0
- package/dist/esm/v1/components/feedback/Toast/Toast.js.map +1 -0
- package/dist/esm/v1/components/forms/FormField/FormField.js.map +1 -1
- package/dist/esm/v1/components/forms/FormGrid/FormGrid.js.map +1 -1
- package/dist/esm/v1/components/layout/AppShell/AppShell.js +89 -0
- package/dist/esm/v1/components/layout/AppShell/AppShell.js.map +1 -0
- package/dist/esm/v1/components/layout/Page/Page.js +16 -0
- package/dist/esm/v1/components/layout/Page/Page.js.map +1 -0
- package/dist/esm/v1/components/layout/PageHeader/PageHeader.js +26 -0
- package/dist/esm/v1/components/layout/PageHeader/PageHeader.js.map +1 -0
- package/dist/esm/v1/components/layout/SideNav/SideNav.js +277 -0
- package/dist/esm/v1/components/layout/SideNav/SideNav.js.map +1 -0
- package/dist/esm/v1/components/layout/navTrail.js +19 -0
- package/dist/esm/v1/components/layout/navTrail.js.map +1 -0
- package/dist/esm/v1/components/lists/ListFinder/ListFinder.js +4 -4
- package/dist/esm/v1/components/lists/ListFinder/ListFinder.js.map +1 -1
- package/dist/esm/v1/components/overlays/ConfirmDialog/ConfirmDialog.js +95 -0
- package/dist/esm/v1/components/overlays/ConfirmDialog/ConfirmDialog.js.map +1 -0
- package/dist/esm/v1/components/overlays/Dialog/Dialog.js.map +1 -1
- package/dist/esm/v1/components/overlays/Tooltip/Tooltip.js +98 -0
- package/dist/esm/v1/components/overlays/Tooltip/Tooltip.js.map +1 -0
- package/dist/esm/v1/components/overlays/Tooltip/TooltipLayer.js +84 -0
- package/dist/esm/v1/components/overlays/Tooltip/TooltipLayer.js.map +1 -0
- package/dist/esm/v1/components/overlays/Tooltip/TooltipPanel.js +33 -0
- package/dist/esm/v1/components/overlays/Tooltip/TooltipPanel.js.map +1 -0
- package/dist/esm/v1/components/overlays/topLayer.js +28 -0
- package/dist/esm/v1/components/overlays/topLayer.js.map +1 -0
- package/dist/esm/v1/components/tables/PaginatedTable/PaginatedTable.js.map +1 -1
- package/dist/esm/v1/components/tables/Table/Table.js.map +1 -1
- package/dist/esm/v1/foundations/icons/icons.js +61 -2
- package/dist/esm/v1/foundations/icons/icons.js.map +1 -1
- package/dist/esm/v1/primitives/buttons/Button/Button.js +18 -4
- package/dist/esm/v1/primitives/buttons/Button/Button.js.map +1 -1
- package/dist/esm/v1/primitives/buttons/SegmentedControl/SegmentedControl.js +65 -0
- package/dist/esm/v1/primitives/buttons/SegmentedControl/SegmentedControl.js.map +1 -0
- package/dist/esm/v1/primitives/buttons/SegmentedControl/segmentedStyles.js +10 -0
- package/dist/esm/v1/primitives/buttons/SegmentedControl/segmentedStyles.js.map +1 -0
- package/dist/esm/v1/primitives/display/Avatar/Avatar.js +120 -0
- package/dist/esm/v1/primitives/display/Avatar/Avatar.js.map +1 -0
- package/dist/esm/v1/primitives/display/Badge/Badge.js +23 -0
- package/dist/esm/v1/primitives/display/Badge/Badge.js.map +1 -0
- package/dist/esm/v1/primitives/display/Card/Card.js +22 -0
- package/dist/esm/v1/primitives/display/Card/Card.js.map +1 -0
- package/dist/esm/v1/primitives/display/FilterChips/FilterChips.js +45 -0
- package/dist/esm/v1/primitives/display/FilterChips/FilterChips.js.map +1 -0
- package/dist/esm/v1/primitives/display/Kpi/Kpi.js +103 -0
- package/dist/esm/v1/primitives/display/Kpi/Kpi.js.map +1 -0
- package/dist/esm/v1/primitives/display/Notice/Notice.js +71 -0
- package/dist/esm/v1/primitives/display/Notice/Notice.js.map +1 -0
- package/dist/esm/v1/primitives/display/Person/Person.js +14 -0
- package/dist/esm/v1/primitives/display/Person/Person.js.map +1 -0
- package/dist/esm/v1/primitives/forms/Checkbox/Checkbox.js +10 -5
- package/dist/esm/v1/primitives/forms/Checkbox/Checkbox.js.map +1 -1
- package/dist/esm/v1/primitives/forms/DateInput/DateInput.js +150 -0
- package/dist/esm/v1/primitives/forms/DateInput/DateInput.js.map +1 -0
- package/dist/esm/v1/primitives/forms/LookupInput/LookupInput.js.map +1 -1
- package/dist/esm/v1/primitives/forms/SearchInput/SearchInput.js +43 -0
- package/dist/esm/v1/primitives/forms/SearchInput/SearchInput.js.map +1 -0
- package/dist/esm/v1/primitives/forms/SelectInput/SelectInput.js +190 -12
- package/dist/esm/v1/primitives/forms/SelectInput/SelectInput.js.map +1 -1
- package/dist/esm/v1/primitives/forms/TextArea/TextArea.js.map +1 -1
- package/dist/esm/v1/primitives/forms/field.js +4 -2
- package/dist/esm/v1/primitives/forms/field.js.map +1 -1
- package/dist/esm/v1/primitives/lists/ListItem/ListItem.js +2 -2
- package/dist/esm/v1/primitives/lists/ListItem/ListItem.js.map +1 -1
- package/dist/esm/v1/primitives/navigation/Tabs/Tabs.js +96 -0
- package/dist/esm/v1/primitives/navigation/Tabs/Tabs.js.map +1 -0
- package/dist/style.components.css +1 -1
- package/dist/style.css +1 -1
- package/dist/style.layered.css +1 -1
- package/dist/tailwind.css +24 -0
- package/dist/theme-boot.js +1 -0
- package/dist/theme.css +10 -3
- package/dist/tokens.css +119 -0
- package/dist/types/legacy/components/atoms/InputData/InputData.d.ts +4 -1
- package/dist/types/legacy/components/charts/Charts.d.ts +58 -5
- package/dist/types/legacy/components/layout/Layout/Layout.d.ts +7 -20
- package/dist/types/legacy/components/layout/Sidebar/Sidebar.d.ts +13 -26
- package/dist/types/legacy/components/molecules/ColumnPicker/ColumnPicker.d.ts +56 -3
- package/dist/types/legacy/components/molecules/DataTable/DataTable.d.ts +72 -7
- package/dist/types/legacy/components/molecules/UserAvatar/UserAvatar.d.ts +4 -5
- package/dist/types/legacy/components/ui/Avisos.d.ts +6 -16
- package/dist/types/legacy/components/ui/CabecalhoPagina.d.ts +6 -20
- package/dist/types/legacy/components/ui/CaixaPesquisa.d.ts +2 -4
- package/dist/types/legacy/components/ui/Confirmacao.d.ts +3 -10
- package/dist/types/legacy/components/ui/Escolha.d.ts +5 -15
- package/dist/types/legacy/components/ui/FaixaAviso.d.ts +2 -6
- package/dist/types/legacy/components/ui/Separadores.d.ts +3 -10
- package/dist/types/legacy/components/ui/Ui.d.ts +12 -54
- package/dist/types/legacy/components/ui/painelFlutuante.d.ts +6 -10
- package/dist/types/legacy/exports/atoms.d.ts +1 -1
- package/dist/types/legacy/exports/charts.d.ts +2 -2
- package/dist/types/legacy/exports/hooks.d.ts +2 -2
- package/dist/types/legacy/exports/molecules.d.ts +2 -2
- package/dist/types/v1/capabilities/overlay/useFloatingPanel.d.ts +20 -0
- package/dist/types/v1/capabilities/theme/useIsDark.d.ts +10 -0
- package/dist/types/v1/capabilities/theme/useTheme.d.ts +29 -0
- package/dist/types/v1/components/data/Pagination/Pagination.d.ts +33 -0
- package/dist/types/v1/components/feedback/Toast/Toast.d.ts +51 -0
- package/dist/types/v1/components/forms/FormField/FormField.d.ts +3 -3
- package/dist/types/v1/components/forms/FormGrid/FormGrid.d.ts +2 -0
- package/dist/types/v1/components/layout/AppShell/AppShell.d.ts +35 -0
- package/dist/types/v1/components/layout/Page/Page.d.ts +23 -0
- package/dist/types/v1/components/layout/PageHeader/PageHeader.d.ts +35 -0
- package/dist/types/v1/components/layout/SideNav/SideNav.d.ts +92 -0
- package/dist/types/v1/components/layout/navTrail.d.ts +15 -0
- package/dist/types/v1/components/lists/ListFinder/ListFinder.d.ts +1 -1
- package/dist/types/v1/components/overlays/ConfirmDialog/ConfirmDialog.d.ts +59 -0
- package/dist/types/v1/components/overlays/Dialog/Dialog.d.ts +6 -3
- package/dist/types/v1/components/overlays/Tooltip/Tooltip.d.ts +34 -0
- package/dist/types/v1/components/overlays/Tooltip/TooltipLayer.d.ts +18 -0
- package/dist/types/v1/components/overlays/Tooltip/TooltipPanel.d.ts +23 -0
- package/dist/types/v1/components/overlays/topLayer.d.ts +23 -0
- package/dist/types/v1/components/tables/PaginatedTable/PaginatedTable.d.ts +1 -1
- package/dist/types/v1/components/tables/Table/Table.d.ts +1 -1
- package/dist/types/v1/foundations/icons/icons.d.ts +20 -0
- package/dist/types/v1/index.d.ts +43 -1
- package/dist/types/v1/primitives/buttons/Button/Button.d.ts +22 -5
- package/dist/types/v1/primitives/buttons/SegmentedControl/SegmentedControl.d.ts +28 -0
- package/dist/types/v1/primitives/buttons/SegmentedControl/segmentedStyles.d.ts +16 -0
- package/dist/types/v1/primitives/display/Avatar/Avatar.d.ts +30 -0
- package/dist/types/v1/primitives/display/Badge/Badge.d.ts +25 -0
- package/dist/types/v1/primitives/display/Card/Card.d.ts +22 -0
- package/dist/types/v1/primitives/display/FilterChips/FilterChips.d.ts +33 -0
- package/dist/types/v1/primitives/display/Kpi/Kpi.d.ts +68 -0
- package/dist/types/v1/primitives/display/Notice/Notice.d.ts +29 -0
- package/dist/types/v1/primitives/display/Person/Person.d.ts +15 -0
- package/dist/types/v1/primitives/forms/Checkbox/Checkbox.d.ts +7 -0
- package/dist/types/v1/primitives/forms/DateInput/DateInput.d.ts +43 -0
- package/dist/types/v1/primitives/forms/LookupInput/LookupInput.d.ts +3 -0
- package/dist/types/v1/primitives/forms/SearchInput/SearchInput.d.ts +23 -0
- package/dist/types/v1/primitives/forms/SelectInput/SelectInput.d.ts +61 -10
- package/dist/types/v1/primitives/forms/TextArea/TextArea.d.ts +3 -0
- package/dist/types/v1/primitives/forms/field.d.ts +2 -0
- package/dist/types/v1/primitives/lists/ListItem/ListItem.d.ts +2 -1
- package/dist/types/v1/primitives/navigation/Tabs/Tabs.d.ts +58 -0
- package/package.json +3 -3
- package/dist/esm/legacy/components/atoms/Tooltip/Tooltip.css +0 -17
- package/dist/esm/legacy/components/atoms/Tooltip/Tooltip.js +0 -41
- package/dist/esm/legacy/components/atoms/Tooltip/Tooltip.js.map +0 -1
- package/dist/esm/legacy/components/molecules/Search/Search.css +0 -18
- package/dist/esm/legacy/components/molecules/Search/Search.js +0 -30
- package/dist/esm/legacy/components/molecules/Search/Search.js.map +0 -1
- package/dist/esm/legacy/hooks/useTheme.js +0 -39
- package/dist/esm/legacy/hooks/useTheme.js.map +0 -1
- package/dist/esm/v1/primitives/lists/ListItem/ListCheckbox.js +0 -28
- package/dist/esm/v1/primitives/lists/ListItem/ListCheckbox.js.map +0 -1
- package/dist/types/legacy/components/atoms/Tooltip/Tooltip.d.ts +0 -13
- package/dist/types/legacy/components/molecules/Search/Search.d.ts +0 -7
- package/dist/types/legacy/hooks/useTheme.d.ts +0 -17
- package/dist/types/v1/primitives/lists/ListItem/ListCheckbox.d.ts +0 -14
|
@@ -1,103 +1,21 @@
|
|
|
1
|
-
import { jsx
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const parts = fullName.trim().split(/\s+/).filter(Boolean);
|
|
7
|
-
if (parts.length === 0) return "U";
|
|
8
|
-
const first = parts[0]?.charAt(0) ?? "";
|
|
9
|
-
const last = (parts.length > 1 ? parts[parts.length - 1]?.charAt(0) : "") ?? "";
|
|
10
|
-
return (first + last).toUpperCase() || "U";
|
|
11
|
-
}
|
|
12
|
-
function UserAvatar({
|
|
13
|
-
name,
|
|
14
|
-
photo,
|
|
15
|
-
email,
|
|
16
|
-
company,
|
|
17
|
-
jobTitle,
|
|
18
|
-
department,
|
|
19
|
-
className = "",
|
|
20
|
-
showCard = false
|
|
21
|
-
}) {
|
|
22
|
-
const ref = useRef(null);
|
|
23
|
-
const [popupPos, setPopupPos] = useState(null);
|
|
24
|
-
const [imgError, setImgError] = useState(false);
|
|
25
|
-
const mediumPhoto = photo?.endsWith("/thumb") ? photo + "/medium" : photo;
|
|
26
|
-
const handleMouseEnter = () => {
|
|
27
|
-
if (!showCard || !ref.current) return;
|
|
28
|
-
const rect = ref.current.getBoundingClientRect();
|
|
29
|
-
const POPUP_W = 200;
|
|
30
|
-
const hasDetails = !!(email || company || jobTitle || department);
|
|
31
|
-
const POPUP_H = hasDetails ? 280 : 196;
|
|
32
|
-
const GAP = 10;
|
|
33
|
-
let left = rect.right + GAP;
|
|
34
|
-
let top = rect.top + rect.height / 2 - POPUP_H / 2;
|
|
35
|
-
if (left + POPUP_W > window.innerWidth - 8) left = rect.left - POPUP_W - GAP;
|
|
36
|
-
if (top + POPUP_H > window.innerHeight - 8) top = window.innerHeight - POPUP_H - 8;
|
|
37
|
-
if (top < 8) top = 8;
|
|
38
|
-
setPopupPos({ top, left });
|
|
39
|
-
};
|
|
40
|
-
const avatar = photo && !imgError ? /* @__PURE__ */ jsx(
|
|
41
|
-
"div",
|
|
42
|
-
{
|
|
43
|
-
ref,
|
|
44
|
-
className: `overflow-hidden shrink-0 cursor-pointer ${className}`,
|
|
45
|
-
onMouseEnter: handleMouseEnter,
|
|
46
|
-
onMouseLeave: () => setPopupPos(null),
|
|
47
|
-
children: /* @__PURE__ */ jsx(
|
|
48
|
-
"img",
|
|
49
|
-
{
|
|
50
|
-
src: photo,
|
|
51
|
-
alt: name || "",
|
|
52
|
-
className: "w-full h-full object-cover rounded-[inherit]",
|
|
53
|
-
onError: () => setImgError(true)
|
|
54
|
-
}
|
|
55
|
-
)
|
|
56
|
-
}
|
|
57
|
-
) : /* @__PURE__ */ jsx(
|
|
58
|
-
"div",
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Avatar } from "../../../../v1/primitives/display/Avatar/Avatar.js";
|
|
3
|
+
function UserAvatar({ name, photo, email, company, jobTitle, department, className = "", showCard = false }) {
|
|
4
|
+
return /* @__PURE__ */ jsx(
|
|
5
|
+
Avatar,
|
|
59
6
|
{
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
7
|
+
size: "none",
|
|
8
|
+
name,
|
|
9
|
+
photo,
|
|
10
|
+
photoLarge: photo?.endsWith("/thumb") ? `${photo}/medium` : photo,
|
|
11
|
+
email,
|
|
12
|
+
company,
|
|
13
|
+
jobTitle,
|
|
14
|
+
department,
|
|
15
|
+
card: showCard,
|
|
16
|
+
className
|
|
65
17
|
}
|
|
66
18
|
);
|
|
67
|
-
const popup = popupPos && createPortal(
|
|
68
|
-
/* @__PURE__ */ jsxs(
|
|
69
|
-
"div",
|
|
70
|
-
{
|
|
71
|
-
style: { position: "fixed", top: popupPos.top, left: popupPos.left, zIndex: 9999, width: 200 },
|
|
72
|
-
className: "pointer-events-none rounded-2xl overflow-hidden shadow-2xl ring-4 ring-white dark:ring-slate-800 border border-slate-100 dark:border-slate-700 bg-white dark:bg-slate-900",
|
|
73
|
-
children: [
|
|
74
|
-
/* @__PURE__ */ jsx("div", { className: "w-48 h-48 rounded-xl overflow-hidden", children: mediumPhoto && !imgError ? /* @__PURE__ */ jsx(
|
|
75
|
-
"img",
|
|
76
|
-
{
|
|
77
|
-
src: mediumPhoto,
|
|
78
|
-
alt: name || "",
|
|
79
|
-
className: "w-full h-full object-cover",
|
|
80
|
-
onError: () => setImgError(true)
|
|
81
|
-
}
|
|
82
|
-
) : /* @__PURE__ */ jsx("div", { className: "w-full h-full flex items-center justify-center bg-gradient-to-br from-slate-200 to-slate-300 text-slate-600 font-black text-4xl", children: getInitials(name || "") }) }),
|
|
83
|
-
/* @__PURE__ */ jsxs("div", { className: "px-3 py-2.5 space-y-1", children: [
|
|
84
|
-
name && /* @__PURE__ */ jsx("p", { className: "text-[12px] font-black text-slate-800 dark:text-slate-100 tracking-tight leading-tight break-words", children: name }),
|
|
85
|
-
email && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-slate-400 truncate", children: email }),
|
|
86
|
-
(company || department || jobTitle) && /* @__PURE__ */ jsxs("div", { className: "pt-1 mt-1 border-t border-slate-50 dark:border-slate-700 space-y-0.5", children: [
|
|
87
|
-
company && /* @__PURE__ */ jsx("p", { className: "text-[10px] font-semibold text-slate-600 dark:text-slate-300 truncate", children: company }),
|
|
88
|
-
department && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-slate-500 dark:text-slate-400 truncate", children: department }),
|
|
89
|
-
jobTitle && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-slate-400 truncate italic", children: jobTitle })
|
|
90
|
-
] })
|
|
91
|
-
] })
|
|
92
|
-
]
|
|
93
|
-
}
|
|
94
|
-
),
|
|
95
|
-
document.body
|
|
96
|
-
);
|
|
97
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
98
|
-
avatar,
|
|
99
|
-
popup
|
|
100
|
-
] });
|
|
101
19
|
}
|
|
102
20
|
export {
|
|
103
21
|
UserAvatar as default
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserAvatar.js","sources":["../../../../../../src/legacy/components/molecules/UserAvatar/UserAvatar.tsx"],"sourcesContent":["import React
|
|
1
|
+
{"version":3,"file":"UserAvatar.js","sources":["../../../../../../src/legacy/components/molecules/UserAvatar/UserAvatar.tsx"],"sourcesContent":["import React from 'react'\r\nimport Avatar from '../../../../v1/primitives/display/Avatar/Avatar'\r\n\r\ninterface UserAvatarProps {\r\n name?: string | null\r\n photo?: string | null\r\n email?: string | null\r\n company?: string | null\r\n jobTitle?: string | null\r\n department?: string | null\r\n /** Tamanho, forma e cores das iniciais (classes Tailwind), como antes. */\r\n className?: string\r\n /** Mostra o cartão de detalhes em hover (foto ampliada + nome/email). Default: off. */\r\n showCard?: boolean\r\n}\r\n\r\n/**\r\n * Migrado para o V1: adaptador do `Avatar` (nomes antigos; o tamanho e as cores vêm do `className`).\r\n * @deprecated Legacy — usar o `Avatar` (ou o `Person`, com o nome ao lado) do V1. Ver Storybook › Legacy.\r\n */\r\nexport default function UserAvatar({ name, photo, email, company, jobTitle, department, className = '', showCard = false }: UserAvatarProps) {\r\n return (\r\n <Avatar\r\n size=\"none\"\r\n name={name}\r\n photo={photo}\r\n photoLarge={photo?.endsWith('/thumb') ? `${photo}/medium` : photo}\r\n email={email}\r\n company={company}\r\n jobTitle={jobTitle}\r\n department={department}\r\n card={showCard}\r\n className={className}\r\n />\r\n )\r\n}"],"names":[],"mappings":";;AAoBA,SAAwB,WAAW,EAAE,MAAM,OAAO,OAAO,SAAS,UAAU,YAAY,YAAY,IAAI,WAAW,MAAA,GAA0B;AAC3I,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA,YAAY,OAAO,SAAS,QAAQ,IAAI,GAAG,KAAK,YAAY;AAAA,MAC5D;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN;AAAA,IAAA;AAAA,EAAA;AAGN;"}
|
|
@@ -393,16 +393,16 @@ function MapaPontos({
|
|
|
393
393
|
suavizar && /* @__PURE__ */ jsx("style", { children: `.mapa-suave .leaflet-tile-pane{filter:grayscale(.85) brightness(1.06) contrast(.95)}` }),
|
|
394
394
|
/* @__PURE__ */ jsx("div", { ref: divRef, style: { height: "100%", width: "100%" }, "aria-label": "Mapa" }),
|
|
395
395
|
pronto && !pontos.length && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center text-sm text-slate-500 pointer-events-none", children: txt.semPontos }),
|
|
396
|
-
!!fundos?.length && /* @__PURE__ */ jsx("div", { className: "absolute top-2 left-12 z-[400] bg-white/90 backdrop-blur-sm shadow-sm rounded flex text-[11px] overflow-hidden", children: fundos.map((f) => /* @__PURE__ */ jsx(
|
|
396
|
+
!!fundos?.length && /* @__PURE__ */ jsx("div", { className: "absolute top-2 left-12 z-[400] bg-white/90 dark:bg-slate-900/90 backdrop-blur-sm shadow-sm rounded flex text-[11px] overflow-hidden", children: fundos.map((f) => /* @__PURE__ */ jsx(
|
|
397
397
|
"button",
|
|
398
398
|
{
|
|
399
399
|
onClick: () => setFundo(f.id),
|
|
400
|
-
className: `px-1.5 py-0.5 ${fundo === f.id ? "bg-proef-navy text-white font-semibold" : "text-slate-600 hover:bg-slate-100"}`,
|
|
400
|
+
className: `px-1.5 py-0.5 ${fundo === f.id ? "bg-proef-navy dark:bg-slate-700 text-white font-semibold" : "text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800"}`,
|
|
401
401
|
children: f.rotulo
|
|
402
402
|
},
|
|
403
403
|
f.id
|
|
404
404
|
)) }),
|
|
405
|
-
legenda && /* @__PURE__ */ jsx("div", { className: "absolute bottom-2 left-2 z-[400] bg-white/90 backdrop-blur-sm rounded shadow-sm px-2 py-1 text-[11px] leading-tight", children: legenda }),
|
|
405
|
+
legenda && /* @__PURE__ */ jsx("div", { className: "absolute bottom-2 left-2 z-[400] bg-white/90 dark:bg-slate-900/90 dark:text-slate-200 backdrop-blur-sm rounded shadow-sm px-2 py-1 text-[11px] leading-tight", children: legenda }),
|
|
406
406
|
!!truncadoEm && txt.truncado && avisoFechadoEm !== truncadoEm && /* @__PURE__ */ jsxs("div", { className: "absolute top-3 right-3 z-[400] flex items-start gap-2 bg-amber-50 border border-amber-300 text-amber-900 shadow rounded-lg px-2.5 py-1 text-[11px] max-w-[22rem]", children: [
|
|
407
407
|
/* @__PURE__ */ jsx("span", { children: txt.truncado.replace("{n}", String(truncadoEm)) }),
|
|
408
408
|
/* @__PURE__ */ jsx(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MapaPontos.js","sources":["../../../../../../src/legacy/components/organisms/MapaPontos/MapaPontos.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'\r\nimport L from 'leaflet'\r\nimport 'leaflet.markercluster'\r\nimport 'leaflet.heat'\r\n\r\n/**\r\n * Mapa de pontos com agrupamento, para listas grandes.\r\n *\r\n * Genérico de propósito: recebe pontos com coordenadas e devolve as funções de\r\n * cor e de rótulo a quem o usa. Não sabe o que é uma ordem de trabalho nem um\r\n * cliente — quem sabe é o ecrã que o monta.\r\n *\r\n * O Leaflet entra por import ESTÁTICO, e não dinâmico.\r\n *\r\n * As duas alternativas falharam, cada uma à sua maneira:\r\n * - import dinâmico + webpack `externals` → o UMD gera\r\n * `try{return require(\"leaflet\")}catch{}`, e no browser não há `require`:\r\n * o catch engole o erro e o componente diz que falta a biblioteca;\r\n * - import dinâmico sem `externals` → o Leaflet fica em chunks separados\r\n * (481.bundle.js) que vivem no node_modules da biblioteca e nunca chegam ao\r\n * dist da aplicação: o pedido devolve o index.html e dá\r\n * `Unexpected token '<'`.\r\n *\r\n * Estático, o bundler da aplicação resolve-o como qualquer outro import. Custa\r\n * ~150 KB a quem não usa mapas — o preço de funcionar sempre.\r\n *\r\n * As folhas de estilo do Leaflet têm de ser importadas pelo projeto:\r\n * import 'leaflet/dist/leaflet.css'\r\n * import 'leaflet.markercluster/dist/MarkerCluster.css'\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\n\r\nexport interface PontoMapa {\r\n id: string\r\n lat: number\r\n lng: number\r\n}\r\n\r\n/**\r\n * Garante que uma camada de tiles fica mesmo visível.\r\n *\r\n * O Leaflet faz os tiles aparecerem com uma animação de opacidade. Se a camada\r\n * for mexida enquanto essa animação decorre — uma troca de fundo, um\r\n * `invalidateSize` fora de horas — há casos em que a opacidade fica em 0 e lá\r\n * fica: os tiles chegam do servidor, entram no DOM, e o mapa continua branco\r\n * sem um único erro na consola.\r\n *\r\n * Repor a opacidade quando a camada termina de carregar custa nada e tira o\r\n * assunto do caminho, mesmo que a causa seja outra.\r\n */\r\nfunction garantirVisivel(camada: any): void {\r\n const repor = (): void => camada.setOpacity?.(1)\r\n camada.on('load', repor)\r\n repor()\r\n}\r\n\r\n/**\r\n * Um fundo de mapa: o que o selector mostra e o que o Leaflet carrega.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport interface FundoMapa {\r\n id: string\r\n rotulo: string\r\n url: string\r\n atribuicao?: string\r\n /** Fundos de satélite ficam melhor sem a dessaturação do tema `suave`. */\r\n semFiltro?: boolean\r\n /**\r\n * Lado do tile em píxeis. 256 por omissão, que é o do OpenStreetMap.\r\n *\r\n * Importa para quem paga por pedido: um tile de 512 cobre a área de quatro\r\n * de 256, e o mesmo ecrã passa a custar um quarto. Com 512 é preciso\r\n * `deslocamentoZoom: -1`, senão o mapa desenha um nível de zoom ao lado.\r\n */\r\n tamanhoTile?: number\r\n deslocamentoZoom?: number\r\n}\r\n\r\n/**\r\n * Uma fatia do anel de um marcador: uma cor e quantos registos ela representa.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport interface FatiaMapa {\r\n cor: string\r\n n: number\r\n /** Nome legível, para a dica nativa do marcador. */\r\n rotulo?: string\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface MapaPontosLabels {\r\n semPontos: string\r\n semBiblioteca: string\r\n aCarregar: string\r\n /** {n} é substituído pelo número de pontos fora do limite. */\r\n truncado?: string\r\n fecharAviso?: string\r\n}\r\n\r\nconst LABELS_POR_OMISSAO: MapaPontosLabels = {\r\n semPontos: 'Sem pontos para mostrar.',\r\n semBiblioteca: 'Mapa indisponível: falta instalar leaflet e leaflet.markercluster.',\r\n aCarregar: 'A carregar o mapa…',\r\n truncado: '{n} pontos não mostrados.',\r\n fecharAviso: 'Fechar aviso',\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface MapaPontosProps<T extends PontoMapa> {\r\n pontos: T[]\r\n /** Cor do marcador. Devolver a mesma cor para todos é perfeitamente válido. */\r\n cor?: (ponto: T) => string\r\n /** HTML do balão. Sem isto não há balão. */\r\n balao?: (ponto: T) => string\r\n /**\r\n * Forma do marcador.\r\n *\r\n * `circulo` para agregados — o tamanho pode variar com a contagem e lê-se\r\n * como densidade. `pin` para registos individuais: a ponta assenta na\r\n * coordenada exata, e é isso que se quer quando o ponto é um sítio e não um\r\n * número.\r\n */\r\n forma?: 'circulo' | 'pin' | 'anel'\r\n\r\n /**\r\n * Composição do marcador, para a forma `anel`.\r\n *\r\n * Um agregado pintado de UMA cor — a do caso mais grave, ou a da contagem —\r\n * diz \"há aqui pelo menos um destes\" e esconde tudo o resto: cinquenta\r\n * registos misturados liam-se como cinquenta urgentes. As fatias mostram a\r\n * composição sem obrigar a abrir o grupo, e deixam a cor dizer SITUAÇÃO\r\n * enquanto o tamanho diz VOLUME — duas perguntas diferentes, dois canais.\r\n */\r\n fatias?: (ponto: T) => FatiaMapa[]\r\n\r\n /**\r\n * Selo por cima do marcador: uma marca que se soma à cor em vez de competir\r\n * com ela (ex: \"está suspensa\"). Null não desenha nada.\r\n */\r\n selo?: (ponto: T) => { texto: string; cor: string; titulo?: string } | null\r\n\r\n /**\r\n * Contorno do marcador, por fora das fatias.\r\n *\r\n * Para uma segunda dimensão que não é exclusiva da primeira — um prazo\r\n * excedido pode acontecer em qualquer estado, e por isso não pode ser uma\r\n * fatia: somá-la ao anel faria as fatias deixarem de somar o total.\r\n */\r\n contorno?: (ponto: T) => string | null\r\n\r\n /** Raio do marcador em píxeis. Só se aplica à forma `circulo`. */\r\n raio?: (ponto: T) => number\r\n /**\r\n * Texto por cima do marcador (ex: a contagem de uma zona).\r\n *\r\n * Pensado para o modo agregado: em vez de desenhar dezenas de milhares de\r\n * pontos, desenha-se um círculo por concelho com o número dentro. O mapa\r\n * deixa de precisar de zoom para dizer alguma coisa.\r\n */\r\n rotulo?: (ponto: T) => string | null\r\n /**\r\n * Marca o ponto como aproximado — desenhado a tracejado.\r\n * Serve para distinguir uma morada confirmada de uma posição estimada, que\r\n * no mapa parecem iguais e não são.\r\n */\r\n aproximado?: (ponto: T) => boolean\r\n onPontoClick?: (ponto: T) => void\r\n\r\n /** Centro inicial. Sem isto, enquadra-se pelos pontos. */\r\n centro?: [number, number]\r\n zoom?: number\r\n altura?: string\r\n\r\n /**\r\n * Aspeto do fundo.\r\n *\r\n * `suave` dessatura os tiles por CSS, deixando os marcadores com cor — num\r\n * mapa de dados, o fundo não deve disputar atenção com o que está em cima.\r\n * Faz o que o basemap claro do CARTO fazia, sem precisar de chave.\r\n */\r\n tema?: 'normal' | 'suave'\r\n\r\n /** Agrupar pontos próximos. Com milhares de pontos, desligar trava o browser. */\r\n agrupar?: boolean\r\n /** Tiles. Por omissão, OpenStreetMap — sem necessidade de chave. */\r\n urlTiles?: string\r\n atribuicaoTiles?: string\r\n\r\n /**\r\n * Fundos alternativos, com um selector no canto do mapa.\r\n *\r\n * Os que precisam de chave ficam de fora por omissão: quem a tiver passa-os\r\n * aqui, e a chave fica do lado de quem a tem em vez de vir na biblioteca.\r\n */\r\n fundos?: FundoMapa[]\r\n\r\n /** Quantos pontos ficaram de fora, para avisar quem vê. */\r\n truncadoEm?: number\r\n\r\n /**\r\n * Um traçado a unir pontos, em [lat, lng].\r\n *\r\n * Serve para mostrar um percurso — a ordem por que se visita, e por onde se\r\n * passa. Sem ele, uma sequência numerada obriga a segui-la com o olho de\r\n * número em número, que é exactamente o trabalho que o mapa devia poupar.\r\n *\r\n * Quem passa o traçado decide o que ele é: se vier de um serviço de rotas,\r\n * são estradas; se forem só as paragens, são linhas rectas. O componente\r\n * desenha o que recebe e não opina — mas quem o usa deve dizer ao\r\n * utilizador qual dos dois é.\r\n */\r\n percurso?: [number, number][] | null\r\n\r\n /**\r\n * Mapa de calor por cima dos pontos.\r\n *\r\n * Responde a outra pergunta: os marcadores dizem o que cada ordem é, o calor\r\n * diz onde há concentração. Com dez mil pontos, contar marcadores não se faz\r\n * — e agrupados, o número do grupo diz quantos mas não onde eles se apertam\r\n * lá dentro.\r\n *\r\n * Soma-se aos marcadores em vez de os substituir: quem liga o calor quer ver\r\n * a mancha E continuar a poder clicar numa ordem.\r\n */\r\n calor?: boolean\r\n\r\n /**\r\n * Quanto cada ponto pesa no calor. Por omissão, um.\r\n *\r\n * Serve para quando um marcador representa muitos — um concelho com 300\r\n * ordens não deve pesar o mesmo que uma ordem solta.\r\n */\r\n pesoCalor?: (p: T) => number\r\n\r\n /**\r\n * Selecção por caixa: `Shift` + arrastar devolve os pontos dentro dela.\r\n *\r\n * Com `Shift` e não só arrastar, porque arrastar já é a forma de deslocar o\r\n * mapa — tirar isso ao arrasto simples trocaria o gesto mais usado pelo mais\r\n * raro.\r\n */\r\n onSeleccionarArea?: (pontos: T[]) => void\r\n\r\n legenda?: React.ReactNode\r\n labels?: Partial<MapaPontosLabels>\r\n className?: string\r\n}\r\n\r\n/**\r\n * Tiles por omissão: OpenStreetMap.\r\n *\r\n * Não precisa de chave. O CARTO, que parecia a escolha óbvia pelo estilo mais\r\n * discreto, passou a exigir API key e os tiles vêm marcados com\r\n * \"API KEY REQUIRED\" por cima do mapa.\r\n *\r\n * Para um estilo próprio (Mapbox, CARTO com conta), passar `urlTiles` e\r\n * `atribuicaoTiles` — a chave fica do lado de quem a tem.\r\n */\r\nconst TILES_OMISSAO = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'\r\nconst ATRIBUICAO_OMISSAO = '© <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a>'\r\n\r\n/** Centro de Portugal continental, para quando não há pontos nem centro indicado. */\r\nconst CENTRO_OMISSAO: [number, number] = [39.6, -8.0]\r\n\r\nconst COR_OMISSAO = '#1F4E79'\r\nconst TAMANHO_PIN = 28\r\n\r\n/**\r\n * Pin em gota, com contorno branco e sombra.\r\n *\r\n * O contorno branco é o que o torna legível sobre qualquer fundo — sem ele, um\r\n * pin escuro desaparece numa zona escura do mapa. Um ponto aproximado fica\r\n * translúcido e com o contorno a tracejado, para não se confundir com uma\r\n * posição confirmada.\r\n */\r\nfunction pinSvg(cor: string, aproximado: boolean): string {\r\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${TAMANHO_PIN}\" height=\"${TAMANHO_PIN}\" viewBox=\"0 0 24 24\"\r\n style=\"filter:drop-shadow(0 1px 2px rgba(0,0,0,.45));opacity:${aproximado ? 0.55 : 1}\">\r\n <path d=\"M12 2C8.13 2 5 5.13 5 9c0 1.36.4 2.63 1.09 3.7L12 23l5.91-10.3A6.94 6.94 0 0 0 19 9c0-3.87-3.13-7-7-7z\"\r\n fill=\"${cor}\" stroke=\"#ffffff\" stroke-width=\"1.2\"${aproximado ? ' stroke-dasharray=\"2 1.5\"' : ''}/>\r\n <circle cx=\"12\" cy=\"9\" r=\"2.6\" fill=\"#ffffff\"/>\r\n </svg>`\r\n}\r\n\r\n/** Escapa o que vai para dentro de um atributo HTML (rótulos vindos dos dados). */\r\nfunction escapar(texto: string): string {\r\n return texto.replace(/[&<>\"']/g, (c) =>\r\n ({ '&': '&', '<': '<', '>': '>', '\"': '"', \"'\": ''' })[c] as string)\r\n}\r\n\r\n/**\r\n * `conic-gradient` com uma fatia por cor, proporcional à contagem.\r\n *\r\n * Os limites de cada fatia levam a mesma percentagem duas vezes (fim da\r\n * anterior, início da seguinte) para o corte ser seco: sem isso o gradiente\r\n * interpola e as cores esfumam-se uma na outra.\r\n */\r\nfunction anelCss(fatias: FatiaMapa[]): string {\r\n const comValor = fatias.filter((f) => f.n > 0)\r\n if (!comValor.length) return '#CBD5E1'\r\n // Uma cor só: poupa ao browser um gradiente por cada marcador no ecrã.\r\n if (comValor.length === 1) return comValor[0]!.cor\r\n const total = comValor.reduce((s, f) => s + f.n, 0)\r\n let acc = 0\r\n const partes = comValor.map((f) => {\r\n const ini = (acc / total) * 100\r\n acc += f.n\r\n return `${f.cor} ${ini.toFixed(2)}% ${((acc / total) * 100).toFixed(2)}%`\r\n })\r\n return `conic-gradient(${partes.join(',')})`\r\n}\r\n\r\n/**\r\n * Marcador em anel: as fatias por fora, o número por dentro.\r\n *\r\n * O branco do meio é o que deixa o número legível seja qual for a composição —\r\n * texto por cima das fatias ficava ilegível em metade dos casos.\r\n */\r\nfunction anelHtml(\r\n fatias: FatiaMapa[],\r\n texto: string,\r\n lado: number,\r\n selo?: { texto: string; cor: string; titulo?: string } | null,\r\n contorno?: string | null,\r\n): string {\r\n // Espessura: o suficiente para a fatia mais fina se ver, sem apertar o número.\r\n const espessura = lado < 32 ? 4 : lado < 44 ? 5 : 7\r\n const titulo = fatias\r\n .filter((f) => f.n > 0)\r\n .map((f) => `${f.n}${f.rotulo ? ` ${f.rotulo}` : ''}`)\r\n .join(' · ')\r\n const d = Math.round(lado * 0.40)\r\n const marca = selo\r\n ? `<div title=\"${escapar(selo.titulo ?? selo.texto)}\" style=\"position:absolute;top:${-d * 0.18}px;right:${-d * 0.18}px;` +\r\n `width:${d}px;height:${d}px;border-radius:9999px;background:${selo.cor};border:1.5px solid #fff;` +\r\n `box-shadow:0 1px 2px rgba(0,0,0,.35);display:flex;align-items:center;justify-content:center;` +\r\n `color:#fff;font:800 ${Math.round(d * 0.66)}px/1 system-ui,sans-serif\">${escapar(selo.texto)}</div>`\r\n : ''\r\n const corpo = Math.max(9, Math.round(lado * (texto.length > 4 ? 0.26 : 0.32)))\r\n // O contorno é desenhado como sombra e não como `border`: um `border` encolhe\r\n // o conteúdo e as fatias mudavam de tamanho conforme houvesse contorno ou não.\r\n const risco = contorno ? `,0 0 0 2.5px ${contorno}` : ''\r\n return `<div title=\"${escapar(titulo)}\" style=\"width:${lado}px;height:${lado}px;border-radius:9999px;` +\r\n `background:${anelCss(fatias)};box-shadow:0 1px 3px rgba(0,0,0,.4)${risco};position:relative\">` +\r\n `<div style=\"position:absolute;inset:${espessura}px;border-radius:9999px;background:#fff;` +\r\n `display:flex;align-items:center;justify-content:center;color:#1e293b;` +\r\n `font:700 ${corpo}px/1 system-ui,sans-serif\">${escapar(texto)}</div>${marca}</div>`\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport default function MapaPontos<T extends PontoMapa>({\r\n pontos,\r\n cor,\r\n balao,\r\n forma = 'circulo',\r\n fatias,\r\n selo,\r\n contorno,\r\n raio,\r\n rotulo,\r\n aproximado,\r\n onPontoClick,\r\n centro,\r\n zoom = 7,\r\n altura = '100%',\r\n agrupar = true,\r\n tema = 'suave',\r\n urlTiles = TILES_OMISSAO,\r\n atribuicaoTiles = ATRIBUICAO_OMISSAO,\r\n truncadoEm,\r\n percurso,\r\n calor = false,\r\n pesoCalor,\r\n fundos,\r\n onSeleccionarArea,\r\n legenda,\r\n labels,\r\n className,\r\n}: MapaPontosProps<T>) {\r\n const txt = { ...LABELS_POR_OMISSAO, ...labels }\r\n const divRef = useRef<HTMLDivElement | null>(null)\r\n const mapaRef = useRef<any>(null)\r\n const camadaRef = useRef<any>(null)\r\n const tilesRef = useRef<any>(null)\r\n /** Os limites dos pontos, para reenquadrar quando o contentor mudar de tamanho. */\r\n const limitesRef = useRef<any>(null)\r\n /** O utilizador já mexeu no mapa? A partir daí a vista é dele. */\r\n const mexidoRef = useRef(false)\r\n /**\r\n * O conjunto de pontos que já foi enquadrado.\r\n *\r\n * O efeito do desenho depende das funções de aspecto (`rotulo`, `fatias`,\r\n * `forma`, `contorno`), que quem usa passa quase sempre inline — e portanto\r\n * mudam de identidade a CADA render do ecrã. Sem esta memória, qualquer\r\n * re-render reenquadrava o mapa: abrir e fechar a ficha de uma ordem\r\n * atirava o zoom de volta para o país inteiro.\r\n */\r\n const chaveEnquadradaRef = useRef<string | null>(null)\r\n\r\n /**\r\n * Um movimento nosso está a decorrer.\r\n *\r\n * O Leaflet dispara `zoomstart` tanto para o gesto da pessoa como para um\r\n * `fitBounds` nosso, e não há como os distinguir pelo evento. Sem esta\r\n * marca, o primeiro enquadramento marcava o mapa como \"já mexido\" e\r\n * bloqueava todos os seguintes.\r\n *\r\n * Dava isto, dentro de um modal: o mapa nasce com altura zero, o\r\n * `fitBounds` calcula contra 0x0 e cai no zoom mínimo, o contentor ganha\r\n * altura logo a seguir — e o reenquadramento que arrumava tudo já estava\r\n * proibido. Ficava o país inteiro.\r\n */\r\n const nossoMovimentoRef = useRef(false)\r\n\r\n /// O valor de `truncadoEm` que a pessoa já dispensou. Guardado o número e\r\n /// não um booleano para o aviso voltar quando o conjunto muda.\r\n const [avisoFechadoEm, setAvisoFechadoEm] = useState<number | null>(null)\r\n const [pronto, setPronto] = useState(false)\r\n const [fundo, setFundo] = useState(fundos?.[0]?.id ?? '')\r\n\r\n /**\r\n * Enquadrar os pontos, sem que isso conte como gesto da pessoa.\r\n *\r\n * A marca cai no `moveend`, que o Leaflet dispara no fim da animação —\r\n * incluindo quando não há animação nenhuma.\r\n */\r\n const enquadrar = useCallback((mapa: L.Map, limites: L.LatLngBounds): void => {\r\n nossoMovimentoRef.current = true\r\n mapa.once('moveend', () => { nossoMovimentoRef.current = false })\r\n mapa.fitBounds(limites, { padding: [24, 24], maxZoom: 14 })\r\n }, [])\r\n\r\n // Criação do mapa — uma só vez.\r\n useEffect(() => {\r\n if (!divRef.current || mapaRef.current) return\r\n\r\n mapaRef.current = L.map(divRef.current, {\r\n center: centro ?? CENTRO_OMISSAO,\r\n zoom,\r\n // Scroll da página não deve dar zoom sem querer; exige Ctrl.\r\n scrollWheelZoom: 'center',\r\n })\r\n const inicial = fundos?.[0]\r\n tilesRef.current = L.tileLayer(inicial?.url ?? urlTiles, {\r\n attribution: inicial?.atribuicao ?? atribuicaoTiles,\r\n maxZoom: 19,\r\n ...(inicial?.tamanhoTile ? { tileSize: inicial.tamanhoTile } : {}),\r\n ...(inicial?.deslocamentoZoom != null ? { zoomOffset: inicial.deslocamentoZoom } : {}),\r\n }).addTo(mapaRef.current)\r\n garantirVisivel(tilesRef.current)\r\n\r\n setPronto(true)\r\n\r\n /*\r\n O Leaflet mede o contentor UMA vez, na criação, e desenha para essa\r\n medida. Quando o mapa nasce antes de o layout assentar — uma coluna que\r\n ainda não tem largura, um painel lateral que abre — fica com metade da\r\n área a cinzento e o conteúdo encostado a um canto, e só se vê tudo\r\n tirando zoom à mão.\r\n\r\n O observador remedia-o a cada mudança de tamanho: apanha o primeiro\r\n assentar do layout, o abrir e fechar dos painéis e o redimensionar da\r\n janela, sem ninguém medir nada.\r\n */\r\n const observador = new ResizeObserver(() => {\r\n mapaRef.current?.invalidateSize()\r\n // Reenquadrar enquanto o utilizador não tiver mexido no mapa: depois\r\n // disso, a vista é dele e mexer-lhe seria roubar-lhe o sítio.\r\n if (!mexidoRef.current && limitesRef.current?.isValid() && mapaRef.current) {\r\n enquadrar(mapaRef.current, limitesRef.current)\r\n }\r\n })\r\n observador.observe(divRef.current)\r\n\r\n // Qualquer gesto do utilizador congela o reenquadramento automático.\r\n const marcarMexido = (): void => {\r\n if (nossoMovimentoRef.current) return\r\n mexidoRef.current = true\r\n }\r\n mapaRef.current.on('zoomstart dragstart', marcarMexido)\r\n\r\n return () => {\r\n observador.disconnect()\r\n mapaRef.current?.remove()\r\n mapaRef.current = null\r\n }\r\n }, [])\r\n\r\n /**\r\n * Selecção por caixa.\r\n *\r\n * O `boxZoom` do Leaflet usa o mesmo gesto (Shift + arrastar) e tem de ser\r\n * desligado, senão o mapa dá zoom à caixa ao mesmo tempo que se selecciona.\r\n */\r\n useEffect(() => {\r\n const mapa = mapaRef.current\r\n if (!mapa) return\r\n\r\n if (!onSeleccionarArea) {\r\n mapa.boxZoom?.enable()\r\n return\r\n }\r\n mapa.boxZoom?.disable()\r\n\r\n let inicio: L.LatLng | null = null\r\n let caixa: HTMLDivElement | null = null\r\n\r\n const posicionar = (a: L.Point, b: L.Point): void => {\r\n if (!caixa) return\r\n caixa.style.left = `${Math.min(a.x, b.x)}px`\r\n caixa.style.top = `${Math.min(a.y, b.y)}px`\r\n caixa.style.width = `${Math.abs(a.x - b.x)}px`\r\n caixa.style.height = `${Math.abs(a.y - b.y)}px`\r\n }\r\n\r\n const aoPremir = (e: L.LeafletMouseEvent): void => {\r\n const original = e.originalEvent as MouseEvent\r\n if (!original.shiftKey) return\r\n inicio = e.latlng\r\n mapa.dragging.disable()\r\n caixa = L.DomUtil.create('div', '', mapa.getPanes().overlayPane) as HTMLDivElement\r\n caixa.style.cssText =\r\n 'position:absolute;border:2px dashed #1F4E79;background:rgba(31,78,121,.12);pointer-events:none;z-index:600'\r\n posicionar(mapa.latLngToLayerPoint(inicio), mapa.latLngToLayerPoint(inicio))\r\n }\r\n\r\n const aoMover = (e: L.LeafletMouseEvent): void => {\r\n if (!inicio || !caixa) return\r\n posicionar(mapa.latLngToLayerPoint(inicio), mapa.latLngToLayerPoint(e.latlng))\r\n }\r\n\r\n const aoLargar = (e: L.LeafletMouseEvent): void => {\r\n if (!inicio) return\r\n const limites = L.latLngBounds(inicio, e.latlng)\r\n caixa?.remove()\r\n caixa = null\r\n inicio = null\r\n mapa.dragging.enable()\r\n // Um clique com Shift não é uma selecção: a caixa teria área zero e\r\n // devolveria lista vazia, o que se leria como \"não há nada aqui\".\r\n if (limites.getNorth() === limites.getSouth()) return\r\n onSeleccionarArea(pontos.filter((p) => limites.contains([p.lat, p.lng])))\r\n }\r\n\r\n mapa.on('mousedown', aoPremir)\r\n mapa.on('mousemove', aoMover)\r\n mapa.on('mouseup', aoLargar)\r\n\r\n return () => {\r\n mapa.off('mousedown', aoPremir)\r\n mapa.off('mousemove', aoMover)\r\n mapa.off('mouseup', aoLargar)\r\n caixa?.remove()\r\n mapa.dragging.enable()\r\n }\r\n }, [pronto, onSeleccionarArea, pontos])\r\n\r\n /*\r\n Troca do fundo.\r\n\r\n Substitui a camada em vez de acrescentar outra por cima — empilhadas, a de\r\n baixo continua a pedir tiles que ninguém vê.\r\n\r\n Duas regras que não são detalhe:\r\n\r\n 1. NÃO mexer quando o fundo já é o que está no mapa. A camada inicial é\r\n criada ao nascer o mapa; se este efeito voltasse a removê-la e a\r\n recriá-la na primeira passagem, estaria a desfazer trabalho acabado de\r\n fazer — e era aí que tudo corria mal.\r\n\r\n 2. A nova camada entra ANTES de a velha sair, e o `invalidateSize` fica\r\n para o quadro seguinte. Remover e recriar no mesmo tique, e logo a\r\n seguir forçar um recálculo de tamanho, apanha o Leaflet a meio da\r\n animação de aparecimento: os tiles chegam do servidor, são inseridos no\r\n DOM e ficam com opacidade 0 para sempre. O mapa fica branco com os\r\n marcadores por cima, e a consola não diz nada porque nada falhou.\r\n */\r\n const fundoAplicadoRef = useRef<string | null>(null)\r\n\r\n useEffect(() => {\r\n const mapa = mapaRef.current\r\n const escolhido = fundos?.find((f) => f.id === fundo)\r\n if (!mapa || !escolhido || !tilesRef.current) return\r\n if (fundoAplicadoRef.current === escolhido.id) return\r\n if (fundoAplicadoRef.current === null) {\r\n // A camada inicial já é esta. Marca-se como aplicada e não se lhe toca.\r\n fundoAplicadoRef.current = escolhido.id\r\n return\r\n }\r\n\r\n /*\r\n Guardar a vista antes de mexer nas camadas: fundos com tamanhos de tile\r\n diferentes (256 e 512) obrigam o Leaflet a recalcular a escala, e entre\r\n uma camada e outra o mapa fica sem referência — era o que fazia os\r\n marcadores saltarem para fora do contentor.\r\n */\r\n const centroActual = mapa.getCenter()\r\n const zoomActual = mapa.getZoom()\r\n const anterior = tilesRef.current\r\n\r\n const nova = L.tileLayer(escolhido.url, {\r\n attribution: escolhido.atribuicao ?? atribuicaoTiles,\r\n maxZoom: 19,\r\n ...(escolhido.tamanhoTile ? { tileSize: escolhido.tamanhoTile } : {}),\r\n ...(escolhido.deslocamentoZoom != null ? { zoomOffset: escolhido.deslocamentoZoom } : {}),\r\n }).addTo(mapa)\r\n // Os tiles pertencem ao painel de baixo; sem isto ficavam por cima dos\r\n // marcadores depois de uma troca.\r\n nova.bringToBack()\r\n garantirVisivel(nova)\r\n\r\n tilesRef.current = nova\r\n fundoAplicadoRef.current = escolhido.id\r\n anterior.remove()\r\n\r\n mapa.setView(centroActual, zoomActual, { animate: false })\r\n requestAnimationFrame(() => mapaRef.current?.invalidateSize())\r\n }, [pronto, fundo])\r\n\r\n /*\r\n O traçado.\r\n\r\n Camada própria e refeita quando o percurso muda, em vez de ir junto com os\r\n marcadores: os pontos redesenham-se a cada filtro e o traçado não tem de\r\n acompanhar esse ritmo.\r\n\r\n Vai para trás dos marcadores — uma linha por cima dos números tapava\r\n justamente o que ela serve para ligar.\r\n */\r\n /*\r\n O mapa de calor.\r\n\r\n Em camada própria e em efeito próprio: liga-se e desliga-se muitas vezes, e\r\n pendurá-lo no desenho dos marcadores obrigava a refazer dez mil marcadores\r\n de cada vez que alguém carregava na caixa.\r\n\r\n Fica POR BAIXO dos marcadores. Por cima, a mancha tapava justamente os\r\n pontos que ela serve para situar.\r\n */\r\n /** Identidade do conjunto de pontos, para não redesenhar o que não mudou. */\r\n const chavePontos = useMemo(() => `${pontos.length}:${pontos[0]?.id ?? ''}:${pontos[pontos.length - 1]?.id ?? ''}`, [pontos])\r\n\r\n const calorRef = useRef<any>(null)\r\n\r\n useEffect(() => {\r\n const mapa = mapaRef.current\r\n if (!mapa) return\r\n\r\n calorRef.current?.remove()\r\n calorRef.current = null\r\n if (!calor) return\r\n\r\n const dados = pontos\r\n .filter((p) => Number.isFinite(p.lat) && Number.isFinite(p.lng))\r\n .map((p) => [p.lat, p.lng, pesoCalor?.(p) ?? 1] as [number, number, number])\r\n if (!dados.length) return\r\n\r\n const heat = (L as any).heatLayer\r\n // Sem o plugin, não se desenha nada em vez de rebentar: o mapa continua a\r\n // servir para tudo o resto.\r\n if (typeof heat !== 'function') return\r\n\r\n calorRef.current = heat(dados, {\r\n radius: 22,\r\n blur: 18,\r\n // Sem tecto, um concelho com 2 000 ordens saturava o país inteiro a\r\n // vermelho e a mancha deixava de distinguir fosse o que fosse.\r\n max: Math.max(1, ...dados.map((d) => d[2])),\r\n minOpacity: 0.25,\r\n })\r\n calorRef.current.addTo(mapa)\r\n // A camada do calor nasce no painel dos overlays, que fica acima dos\r\n // marcadores; desce-se para os pontos continuarem clicáveis e visíveis.\r\n calorRef.current.getPane?.()\r\n const el = calorRef.current._canvas as HTMLElement | undefined\r\n if (el) el.style.zIndex = '350'\r\n\r\n return () => {\r\n calorRef.current?.remove()\r\n calorRef.current = null\r\n }\r\n }, [pronto, calor, chavePontos, pesoCalor])\r\n\r\n const percursoRef = useRef<any>(null)\r\n\r\n useEffect(() => {\r\n const mapa = mapaRef.current\r\n if (!mapa) return\r\n\r\n percursoRef.current?.remove()\r\n percursoRef.current = null\r\n if (!percurso?.length) return\r\n\r\n percursoRef.current = L.polyline(percurso, {\r\n color: '#1F4E79',\r\n weight: 3,\r\n opacity: 0.75,\r\n // Tracejado: lembra que é um percurso proposto, não um registo do que\r\n // foi feito.\r\n dashArray: '6 6',\r\n }).addTo(mapa)\r\n percursoRef.current.bringToBack()\r\n tilesRef.current?.bringToBack()\r\n }, [pronto, percurso])\r\n\r\n\r\n // Desenho dos pontos — refeito quando a lista muda.\r\n useEffect(() => {\r\n const mapa = mapaRef.current\r\n if (!mapa) return\r\n\r\n camadaRef.current?.remove()\r\n\r\n /**\r\n * Ícone do grupo: o mesmo anel dos marcadores, com a composição somada.\r\n *\r\n * Fechar pontos não pode apagar o que eles diziam — o grupo azul por\r\n * omissão do markercluster diz quantos, e nada sobre o que são.\r\n */\r\n const iconeGrupo = (c: any) => {\r\n const filhos: T[] = c.getAllChildMarkers()\r\n .map((mk: any) => mk.options.ponto)\r\n .filter(Boolean)\r\n const soma = new Map<string, FatiaMapa>()\r\n for (const f of filhos.flatMap((p) => fatias?.(p) ?? [{ cor: cor?.(p) ?? COR_OMISSAO, n: 1 }])) {\r\n const anterior = soma.get(f.cor)\r\n if (anterior) anterior.n += f.n\r\n else soma.set(f.cor, { ...f })\r\n }\r\n // Cada filho conta o que as suas fatias somam; sem fatias, conta por um.\r\n const total = filhos.length\r\n ? filhos.reduce((n, p) => n + ((fatias?.(p) ?? []).reduce((x, f) => x + f.n, 0) || 1), 0)\r\n : c.getChildCount()\r\n const lado = total < 10 ? 26 : total < 100 ? 32 : total < 1000 ? 38 : 46\r\n return L.divIcon({\r\n className: '',\r\n iconSize: [lado, lado],\r\n html: anelHtml([...soma.values()], total.toLocaleString('pt-PT'), lado),\r\n })\r\n }\r\n\r\n const camada = agrupar && (L as any).markerClusterGroup\r\n ? (L as any).markerClusterGroup({\r\n chunkedLoading: true,\r\n maxClusterRadius: 50,\r\n showCoverageOnHover: false,\r\n // Sem isto, marcadores na MESMA coordenada nunca se separam por muito\r\n // zoom que se faça, e os de baixo ficam inalcançáveis.\r\n spiderfyOnMaxZoom: true,\r\n ...(forma === 'anel' || fatias ? { iconCreateFunction: iconeGrupo } : {}),\r\n })\r\n : L.layerGroup()\r\n\r\n for (const p of pontos) {\r\n if (!Number.isFinite(p.lat) || !Number.isFinite(p.lng)) continue\r\n const estimado = aproximado?.(p) ?? false\r\n\r\n if (forma === 'anel') {\r\n const composicao = fatias?.(p) ?? [{ cor: cor?.(p) ?? COR_OMISSAO, n: 1 }]\r\n // Diâmetro: `raio` continua a ser o raio, para o `raio` dos outros\r\n // modos querer dizer a mesma coisa aqui.\r\n const lado = Math.round((raio?.(p) ?? 18) * 2)\r\n const marcadorAnel = L.marker([p.lat, p.lng], {\r\n icon: L.divIcon({\r\n className: '',\r\n iconSize: [lado, lado],\r\n iconAnchor: [lado / 2, lado / 2],\r\n popupAnchor: [0, -lado / 2],\r\n html: anelHtml(composicao, rotulo?.(p) ?? '', lado, selo?.(p), contorno?.(p)),\r\n }),\r\n })\r\n const htmlAnel = balao?.(p)\r\n if (htmlAnel) marcadorAnel.bindPopup(htmlAnel)\r\n if (onPontoClick) marcadorAnel.on('click', () => onPontoClick(p))\r\n ;(marcadorAnel.options as { ponto?: T }).ponto = p\r\n camada.addLayer(marcadorAnel)\r\n continue\r\n }\r\n\r\n const marcador = forma === 'pin'\r\n ? L.marker([p.lat, p.lng], {\r\n icon: L.divIcon({\r\n className: '',\r\n // A ponta do pin é que assenta na coordenada, não o centro do\r\n // ícone — daí a âncora no fundo, a meio da largura.\r\n iconSize: [TAMANHO_PIN, TAMANHO_PIN],\r\n iconAnchor: [TAMANHO_PIN / 2, TAMANHO_PIN],\r\n popupAnchor: [0, -TAMANHO_PIN + 4],\r\n html: pinSvg(cor?.(p) ?? COR_OMISSAO, estimado),\r\n }),\r\n })\r\n : L.circleMarker([p.lat, p.lng], {\r\n radius: raio?.(p) ?? 6,\r\n color: cor?.(p) ?? COR_OMISSAO,\r\n weight: estimado ? 1 : 2,\r\n // Tracejado e mais transparente: a posição é estimada, e o mapa não\r\n // deve sugerir uma precisão que não tem.\r\n dashArray: estimado ? '2,3' : undefined,\r\n fillOpacity: estimado ? 0.25 : 0.75,\r\n fillColor: cor?.(p) ?? COR_OMISSAO,\r\n })\r\n ;(marcador.options as { ponto?: T }).ponto = p\r\n const html = balao?.(p)\r\n if (html) marcador.bindPopup(html)\r\n if (onPontoClick) marcador.on('click', () => onPontoClick(p))\r\n camada.addLayer(marcador)\r\n\r\n const texto = rotulo?.(p)\r\n if (texto) {\r\n // `interactive: false` para o rótulo não roubar o clique ao círculo\r\n // que está por baixo — senão clicar no número não faz nada.\r\n camada.addLayer(\r\n L.marker([p.lat, p.lng], {\r\n interactive: false,\r\n icon: L.divIcon({\r\n className: '',\r\n iconSize: [0, 0],\r\n html:\r\n `<div style=\"transform:translate(-50%,-50%);white-space:nowrap;font:700 11px/1 system-ui,sans-serif;` +\r\n `color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.7);pointer-events:none\">${texto}</div>`,\r\n }),\r\n })\r\n )\r\n }\r\n }\r\n\r\n camada.addTo(mapa)\r\n camadaRef.current = camada\r\n\r\n if (pontos.length) {\r\n const limites = L.latLngBounds(pontos.map((p) => [p.lat, p.lng]))\r\n limitesRef.current = limites.isValid() ? limites : null\r\n /*\r\n Pontos novos são um conjunto novo: vale a pena voltar a enquadrar,\r\n mesmo que o utilizador já tenha mexido — senão um filtro que reduz a\r\n 3 pontos deixava o mapa onde estava, aparentemente vazio.\r\n\r\n Mas só quando o CONJUNTO muda. Um re-render que apenas trocou a\r\n identidade de uma função de aspecto não é um conjunto novo, e tratá-lo\r\n como tal desfazia o zoom que a pessoa tinha acabado de dar.\r\n */\r\n if (limitesRef.current && !centro && chaveEnquadradaRef.current !== chavePontos) {\r\n chaveEnquadradaRef.current = chavePontos\r\n mexidoRef.current = false\r\n enquadrar(mapa, limitesRef.current)\r\n }\r\n }\r\n }, [pronto, chavePontos, agrupar, rotulo, forma, fatias, contorno])\r\n\r\n // Dessaturar o fundo? Só no tema suave, e nunca num satélite.\r\n const suavizar = tema === 'suave' && !fundos?.find((f) => f.id === fundo)?.semFiltro\r\n\r\n return (\r\n /*\r\n `overflow-hidden` e `isolate` não são decoração.\r\n\r\n O CORTE: os painéis do Leaflet são absolutos e deslocados por\r\n transformações. Se o mapa ficar com uma posição interna inconsistente —\r\n ao trocar de fundo, por exemplo — os marcadores pintam-se por cima do\r\n resto da página. Aconteceu. O corte garante que o pior caso é um mapa\r\n vazio, não a página desfeita.\r\n\r\n O ISOLAMENTO: o Leaflet dá z-index 1000 aos seus controlos e 400 aos\r\n painéis. `relative` sozinho NÃO cria contexto de empilhamento — só o cria\r\n com um `z-index` — por isso esses valores subiam até à raiz da página e\r\n competiam de igual para igual com tudo o resto. Um modal a z-50 ficava por\r\n baixo do mapa: abria-se uma ordem e o mapa pintava-se por cima da ficha.\r\n\r\n `isolate` fecha-os cá dentro. O mapa passa a valer como um elemento só na\r\n ordem de empilhamento da página, e deixa de haver maneira de uma peça\r\n interna dele passar à frente de um modal, de um menu ou de um tooltip.\r\n */\r\n <div\r\n className={`relative isolate overflow-hidden ${suavizar ? 'mapa-suave' : ''} ${className ?? ''}`}\r\n style={{ height: altura }}\r\n >\r\n {/* O filtro aplica-se SÓ ao painel dos tiles: os marcadores vivem em\r\n painéis acima e mantêm a cor. */}\r\n {suavizar && (\r\n <style>{`.mapa-suave .leaflet-tile-pane{filter:grayscale(.85) brightness(1.06) contrast(.95)}`}</style>\r\n )}\r\n {/*\r\n O contentor do Leaflet NÃO leva `className` do React.\r\n\r\n O Leaflet escreve as classes dele neste mesmo elemento\r\n (`leaflet-container`, `leaflet-grab`, `leaflet-fade-anim`…). O React\r\n controla o atributo `className` inteiro: bastava a condição mudar — ao\r\n trocar para um fundo de satélite, que dispensa a dessaturação — para\r\n ele reescrever o atributo e varrer as classes do Leaflet.\r\n\r\n Sem `leaflet-container`, os painéis perdem o posicionamento e o mapa\r\n fica branco com os marcadores por cima. E não voltava: ao trocar de\r\n novo, o React só reescrevia outra vez.\r\n\r\n A classe do filtro vive no invólucro, que é do React e mais ninguém\r\n lhe toca.\r\n */}\r\n <div ref={divRef} style={{ height: '100%', width: '100%' }} aria-label=\"Mapa\" />\r\n\r\n {pronto && !pontos.length && (\r\n <div className=\"absolute inset-0 flex items-center justify-center text-sm text-slate-500 pointer-events-none\">\r\n {txt.semPontos}\r\n </div>\r\n )}\r\n\r\n {!!fundos?.length && (\r\n <div className=\"absolute top-2 left-12 z-[400] bg-white/90 backdrop-blur-sm shadow-sm rounded flex text-[11px] overflow-hidden\">\r\n {fundos.map((f) => (\r\n <button\r\n key={f.id}\r\n onClick={() => setFundo(f.id)}\r\n className={`px-1.5 py-0.5 ${\r\n fundo === f.id ? 'bg-proef-navy text-white font-semibold' : 'text-slate-600 hover:bg-slate-100'\r\n }`}\r\n >\r\n {f.rotulo}\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n\r\n {/* Compacta e encostada: a legenda explica o mapa, não o tapa. */}\r\n {legenda && (\r\n <div className=\"absolute bottom-2 left-2 z-[400] bg-white/90 backdrop-blur-sm rounded shadow-sm px-2 py-1 text-[11px] leading-tight\">\r\n {legenda}\r\n </div>\r\n )}\r\n\r\n {/*\r\n O aviso de truncagem fecha-se.\r\n\r\n Diz uma coisa importante na primeira vez — que o mapa não está a\r\n mostrar tudo — e nada de novo das vezes seguintes. Enquanto não se\r\n podia fechar, tapava o canto do mapa durante toda a sessão.\r\n\r\n Volta a aparecer quando o número muda: aí é um aviso diferente, sobre\r\n um conjunto diferente.\r\n */}\r\n {!!truncadoEm && txt.truncado && avisoFechadoEm !== truncadoEm && (\r\n <div className=\"absolute top-3 right-3 z-[400] flex items-start gap-2 bg-amber-50 border border-amber-300 text-amber-900 shadow rounded-lg px-2.5 py-1 text-[11px] max-w-[22rem]\">\r\n <span>{txt.truncado.replace('{n}', String(truncadoEm))}</span>\r\n <button\r\n onClick={() => setAvisoFechadoEm(truncadoEm)}\r\n aria-label={txt.fecharAviso ?? 'Fechar'}\r\n title={txt.fecharAviso ?? 'Fechar'}\r\n className=\"text-amber-700 hover:text-amber-900 leading-none font-bold\"\r\n >\r\n ×\r\n </button>\r\n </div>\r\n )}\r\n </div>\r\n )\r\n}\r\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MapaPontos.js","sources":["../../../../../../src/legacy/components/organisms/MapaPontos/MapaPontos.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'\r\nimport L from 'leaflet'\r\nimport 'leaflet.markercluster'\r\nimport 'leaflet.heat'\r\n\r\n/**\r\n * Mapa de pontos com agrupamento, para listas grandes.\r\n *\r\n * Genérico de propósito: recebe pontos com coordenadas e devolve as funções de\r\n * cor e de rótulo a quem o usa. Não sabe o que é uma ordem de trabalho nem um\r\n * cliente — quem sabe é o ecrã que o monta.\r\n *\r\n * O Leaflet entra por import ESTÁTICO, e não dinâmico.\r\n *\r\n * As duas alternativas falharam, cada uma à sua maneira:\r\n * - import dinâmico + webpack `externals` → o UMD gera\r\n * `try{return require(\"leaflet\")}catch{}`, e no browser não há `require`:\r\n * o catch engole o erro e o componente diz que falta a biblioteca;\r\n * - import dinâmico sem `externals` → o Leaflet fica em chunks separados\r\n * (481.bundle.js) que vivem no node_modules da biblioteca e nunca chegam ao\r\n * dist da aplicação: o pedido devolve o index.html e dá\r\n * `Unexpected token '<'`.\r\n *\r\n * Estático, o bundler da aplicação resolve-o como qualquer outro import. Custa\r\n * ~150 KB a quem não usa mapas — o preço de funcionar sempre.\r\n *\r\n * As folhas de estilo do Leaflet têm de ser importadas pelo projeto:\r\n * import 'leaflet/dist/leaflet.css'\r\n * import 'leaflet.markercluster/dist/MarkerCluster.css'\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\n\r\nexport interface PontoMapa {\r\n id: string\r\n lat: number\r\n lng: number\r\n}\r\n\r\n/**\r\n * Garante que uma camada de tiles fica mesmo visível.\r\n *\r\n * O Leaflet faz os tiles aparecerem com uma animação de opacidade. Se a camada\r\n * for mexida enquanto essa animação decorre — uma troca de fundo, um\r\n * `invalidateSize` fora de horas — há casos em que a opacidade fica em 0 e lá\r\n * fica: os tiles chegam do servidor, entram no DOM, e o mapa continua branco\r\n * sem um único erro na consola.\r\n *\r\n * Repor a opacidade quando a camada termina de carregar custa nada e tira o\r\n * assunto do caminho, mesmo que a causa seja outra.\r\n */\r\nfunction garantirVisivel(camada: any): void {\r\n const repor = (): void => camada.setOpacity?.(1)\r\n camada.on('load', repor)\r\n repor()\r\n}\r\n\r\n/**\r\n * Um fundo de mapa: o que o selector mostra e o que o Leaflet carrega.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport interface FundoMapa {\r\n id: string\r\n rotulo: string\r\n url: string\r\n atribuicao?: string\r\n /** Fundos de satélite ficam melhor sem a dessaturação do tema `suave`. */\r\n semFiltro?: boolean\r\n /**\r\n * Lado do tile em píxeis. 256 por omissão, que é o do OpenStreetMap.\r\n *\r\n * Importa para quem paga por pedido: um tile de 512 cobre a área de quatro\r\n * de 256, e o mesmo ecrã passa a custar um quarto. Com 512 é preciso\r\n * `deslocamentoZoom: -1`, senão o mapa desenha um nível de zoom ao lado.\r\n */\r\n tamanhoTile?: number\r\n deslocamentoZoom?: number\r\n}\r\n\r\n/**\r\n * Uma fatia do anel de um marcador: uma cor e quantos registos ela representa.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport interface FatiaMapa {\r\n cor: string\r\n n: number\r\n /** Nome legível, para a dica nativa do marcador. */\r\n rotulo?: string\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface MapaPontosLabels {\r\n semPontos: string\r\n semBiblioteca: string\r\n aCarregar: string\r\n /** {n} é substituído pelo número de pontos fora do limite. */\r\n truncado?: string\r\n fecharAviso?: string\r\n}\r\n\r\nconst LABELS_POR_OMISSAO: MapaPontosLabels = {\r\n semPontos: 'Sem pontos para mostrar.',\r\n semBiblioteca: 'Mapa indisponível: falta instalar leaflet e leaflet.markercluster.',\r\n aCarregar: 'A carregar o mapa…',\r\n truncado: '{n} pontos não mostrados.',\r\n fecharAviso: 'Fechar aviso',\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface MapaPontosProps<T extends PontoMapa> {\r\n pontos: T[]\r\n /** Cor do marcador. Devolver a mesma cor para todos é perfeitamente válido. */\r\n cor?: (ponto: T) => string\r\n /** HTML do balão. Sem isto não há balão. */\r\n balao?: (ponto: T) => string\r\n /**\r\n * Forma do marcador.\r\n *\r\n * `circulo` para agregados — o tamanho pode variar com a contagem e lê-se\r\n * como densidade. `pin` para registos individuais: a ponta assenta na\r\n * coordenada exata, e é isso que se quer quando o ponto é um sítio e não um\r\n * número.\r\n */\r\n forma?: 'circulo' | 'pin' | 'anel'\r\n\r\n /**\r\n * Composição do marcador, para a forma `anel`.\r\n *\r\n * Um agregado pintado de UMA cor — a do caso mais grave, ou a da contagem —\r\n * diz \"há aqui pelo menos um destes\" e esconde tudo o resto: cinquenta\r\n * registos misturados liam-se como cinquenta urgentes. As fatias mostram a\r\n * composição sem obrigar a abrir o grupo, e deixam a cor dizer SITUAÇÃO\r\n * enquanto o tamanho diz VOLUME — duas perguntas diferentes, dois canais.\r\n */\r\n fatias?: (ponto: T) => FatiaMapa[]\r\n\r\n /**\r\n * Selo por cima do marcador: uma marca que se soma à cor em vez de competir\r\n * com ela (ex: \"está suspensa\"). Null não desenha nada.\r\n */\r\n selo?: (ponto: T) => { texto: string; cor: string; titulo?: string } | null\r\n\r\n /**\r\n * Contorno do marcador, por fora das fatias.\r\n *\r\n * Para uma segunda dimensão que não é exclusiva da primeira — um prazo\r\n * excedido pode acontecer em qualquer estado, e por isso não pode ser uma\r\n * fatia: somá-la ao anel faria as fatias deixarem de somar o total.\r\n */\r\n contorno?: (ponto: T) => string | null\r\n\r\n /** Raio do marcador em píxeis. Só se aplica à forma `circulo`. */\r\n raio?: (ponto: T) => number\r\n /**\r\n * Texto por cima do marcador (ex: a contagem de uma zona).\r\n *\r\n * Pensado para o modo agregado: em vez de desenhar dezenas de milhares de\r\n * pontos, desenha-se um círculo por concelho com o número dentro. O mapa\r\n * deixa de precisar de zoom para dizer alguma coisa.\r\n */\r\n rotulo?: (ponto: T) => string | null\r\n /**\r\n * Marca o ponto como aproximado — desenhado a tracejado.\r\n * Serve para distinguir uma morada confirmada de uma posição estimada, que\r\n * no mapa parecem iguais e não são.\r\n */\r\n aproximado?: (ponto: T) => boolean\r\n onPontoClick?: (ponto: T) => void\r\n\r\n /** Centro inicial. Sem isto, enquadra-se pelos pontos. */\r\n centro?: [number, number]\r\n zoom?: number\r\n altura?: string\r\n\r\n /**\r\n * Aspeto do fundo.\r\n *\r\n * `suave` dessatura os tiles por CSS, deixando os marcadores com cor — num\r\n * mapa de dados, o fundo não deve disputar atenção com o que está em cima.\r\n * Faz o que o basemap claro do CARTO fazia, sem precisar de chave.\r\n */\r\n tema?: 'normal' | 'suave'\r\n\r\n /** Agrupar pontos próximos. Com milhares de pontos, desligar trava o browser. */\r\n agrupar?: boolean\r\n /** Tiles. Por omissão, OpenStreetMap — sem necessidade de chave. */\r\n urlTiles?: string\r\n atribuicaoTiles?: string\r\n\r\n /**\r\n * Fundos alternativos, com um selector no canto do mapa.\r\n *\r\n * Os que precisam de chave ficam de fora por omissão: quem a tiver passa-os\r\n * aqui, e a chave fica do lado de quem a tem em vez de vir na biblioteca.\r\n */\r\n fundos?: FundoMapa[]\r\n\r\n /** Quantos pontos ficaram de fora, para avisar quem vê. */\r\n truncadoEm?: number\r\n\r\n /**\r\n * Um traçado a unir pontos, em [lat, lng].\r\n *\r\n * Serve para mostrar um percurso — a ordem por que se visita, e por onde se\r\n * passa. Sem ele, uma sequência numerada obriga a segui-la com o olho de\r\n * número em número, que é exactamente o trabalho que o mapa devia poupar.\r\n *\r\n * Quem passa o traçado decide o que ele é: se vier de um serviço de rotas,\r\n * são estradas; se forem só as paragens, são linhas rectas. O componente\r\n * desenha o que recebe e não opina — mas quem o usa deve dizer ao\r\n * utilizador qual dos dois é.\r\n */\r\n percurso?: [number, number][] | null\r\n\r\n /**\r\n * Mapa de calor por cima dos pontos.\r\n *\r\n * Responde a outra pergunta: os marcadores dizem o que cada ordem é, o calor\r\n * diz onde há concentração. Com dez mil pontos, contar marcadores não se faz\r\n * — e agrupados, o número do grupo diz quantos mas não onde eles se apertam\r\n * lá dentro.\r\n *\r\n * Soma-se aos marcadores em vez de os substituir: quem liga o calor quer ver\r\n * a mancha E continuar a poder clicar numa ordem.\r\n */\r\n calor?: boolean\r\n\r\n /**\r\n * Quanto cada ponto pesa no calor. Por omissão, um.\r\n *\r\n * Serve para quando um marcador representa muitos — um concelho com 300\r\n * ordens não deve pesar o mesmo que uma ordem solta.\r\n */\r\n pesoCalor?: (p: T) => number\r\n\r\n /**\r\n * Selecção por caixa: `Shift` + arrastar devolve os pontos dentro dela.\r\n *\r\n * Com `Shift` e não só arrastar, porque arrastar já é a forma de deslocar o\r\n * mapa — tirar isso ao arrasto simples trocaria o gesto mais usado pelo mais\r\n * raro.\r\n */\r\n onSeleccionarArea?: (pontos: T[]) => void\r\n\r\n legenda?: React.ReactNode\r\n labels?: Partial<MapaPontosLabels>\r\n className?: string\r\n}\r\n\r\n/**\r\n * Tiles por omissão: OpenStreetMap.\r\n *\r\n * Não precisa de chave. O CARTO, que parecia a escolha óbvia pelo estilo mais\r\n * discreto, passou a exigir API key e os tiles vêm marcados com\r\n * \"API KEY REQUIRED\" por cima do mapa.\r\n *\r\n * Para um estilo próprio (Mapbox, CARTO com conta), passar `urlTiles` e\r\n * `atribuicaoTiles` — a chave fica do lado de quem a tem.\r\n */\r\nconst TILES_OMISSAO = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'\r\nconst ATRIBUICAO_OMISSAO = '© <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a>'\r\n\r\n/** Centro de Portugal continental, para quando não há pontos nem centro indicado. */\r\nconst CENTRO_OMISSAO: [number, number] = [39.6, -8.0]\r\n\r\nconst COR_OMISSAO = '#1F4E79'\r\nconst TAMANHO_PIN = 28\r\n\r\n/**\r\n * Pin em gota, com contorno branco e sombra.\r\n *\r\n * O contorno branco é o que o torna legível sobre qualquer fundo — sem ele, um\r\n * pin escuro desaparece numa zona escura do mapa. Um ponto aproximado fica\r\n * translúcido e com o contorno a tracejado, para não se confundir com uma\r\n * posição confirmada.\r\n */\r\nfunction pinSvg(cor: string, aproximado: boolean): string {\r\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${TAMANHO_PIN}\" height=\"${TAMANHO_PIN}\" viewBox=\"0 0 24 24\"\r\n style=\"filter:drop-shadow(0 1px 2px rgba(0,0,0,.45));opacity:${aproximado ? 0.55 : 1}\">\r\n <path d=\"M12 2C8.13 2 5 5.13 5 9c0 1.36.4 2.63 1.09 3.7L12 23l5.91-10.3A6.94 6.94 0 0 0 19 9c0-3.87-3.13-7-7-7z\"\r\n fill=\"${cor}\" stroke=\"#ffffff\" stroke-width=\"1.2\"${aproximado ? ' stroke-dasharray=\"2 1.5\"' : ''}/>\r\n <circle cx=\"12\" cy=\"9\" r=\"2.6\" fill=\"#ffffff\"/>\r\n </svg>`\r\n}\r\n\r\n/** Escapa o que vai para dentro de um atributo HTML (rótulos vindos dos dados). */\r\nfunction escapar(texto: string): string {\r\n return texto.replace(/[&<>\"']/g, (c) =>\r\n ({ '&': '&', '<': '<', '>': '>', '\"': '"', \"'\": ''' })[c] as string)\r\n}\r\n\r\n/**\r\n * `conic-gradient` com uma fatia por cor, proporcional à contagem.\r\n *\r\n * Os limites de cada fatia levam a mesma percentagem duas vezes (fim da\r\n * anterior, início da seguinte) para o corte ser seco: sem isso o gradiente\r\n * interpola e as cores esfumam-se uma na outra.\r\n */\r\nfunction anelCss(fatias: FatiaMapa[]): string {\r\n const comValor = fatias.filter((f) => f.n > 0)\r\n if (!comValor.length) return '#CBD5E1'\r\n // Uma cor só: poupa ao browser um gradiente por cada marcador no ecrã.\r\n if (comValor.length === 1) return comValor[0]!.cor\r\n const total = comValor.reduce((s, f) => s + f.n, 0)\r\n let acc = 0\r\n const partes = comValor.map((f) => {\r\n const ini = (acc / total) * 100\r\n acc += f.n\r\n return `${f.cor} ${ini.toFixed(2)}% ${((acc / total) * 100).toFixed(2)}%`\r\n })\r\n return `conic-gradient(${partes.join(',')})`\r\n}\r\n\r\n/**\r\n * Marcador em anel: as fatias por fora, o número por dentro.\r\n *\r\n * O branco do meio é o que deixa o número legível seja qual for a composição —\r\n * texto por cima das fatias ficava ilegível em metade dos casos.\r\n */\r\nfunction anelHtml(\r\n fatias: FatiaMapa[],\r\n texto: string,\r\n lado: number,\r\n selo?: { texto: string; cor: string; titulo?: string } | null,\r\n contorno?: string | null,\r\n): string {\r\n // Espessura: o suficiente para a fatia mais fina se ver, sem apertar o número.\r\n const espessura = lado < 32 ? 4 : lado < 44 ? 5 : 7\r\n const titulo = fatias\r\n .filter((f) => f.n > 0)\r\n .map((f) => `${f.n}${f.rotulo ? ` ${f.rotulo}` : ''}`)\r\n .join(' · ')\r\n const d = Math.round(lado * 0.40)\r\n const marca = selo\r\n ? `<div title=\"${escapar(selo.titulo ?? selo.texto)}\" style=\"position:absolute;top:${-d * 0.18}px;right:${-d * 0.18}px;` +\r\n `width:${d}px;height:${d}px;border-radius:9999px;background:${selo.cor};border:1.5px solid #fff;` +\r\n `box-shadow:0 1px 2px rgba(0,0,0,.35);display:flex;align-items:center;justify-content:center;` +\r\n `color:#fff;font:800 ${Math.round(d * 0.66)}px/1 system-ui,sans-serif\">${escapar(selo.texto)}</div>`\r\n : ''\r\n const corpo = Math.max(9, Math.round(lado * (texto.length > 4 ? 0.26 : 0.32)))\r\n // O contorno é desenhado como sombra e não como `border`: um `border` encolhe\r\n // o conteúdo e as fatias mudavam de tamanho conforme houvesse contorno ou não.\r\n const risco = contorno ? `,0 0 0 2.5px ${contorno}` : ''\r\n return `<div title=\"${escapar(titulo)}\" style=\"width:${lado}px;height:${lado}px;border-radius:9999px;` +\r\n `background:${anelCss(fatias)};box-shadow:0 1px 3px rgba(0,0,0,.4)${risco};position:relative\">` +\r\n `<div style=\"position:absolute;inset:${espessura}px;border-radius:9999px;background:#fff;` +\r\n `display:flex;align-items:center;justify-content:center;color:#1e293b;` +\r\n `font:700 ${corpo}px/1 system-ui,sans-serif\">${escapar(texto)}</div>${marca}</div>`\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport default function MapaPontos<T extends PontoMapa>({\r\n pontos,\r\n cor,\r\n balao,\r\n forma = 'circulo',\r\n fatias,\r\n selo,\r\n contorno,\r\n raio,\r\n rotulo,\r\n aproximado,\r\n onPontoClick,\r\n centro,\r\n zoom = 7,\r\n altura = '100%',\r\n agrupar = true,\r\n tema = 'suave',\r\n urlTiles = TILES_OMISSAO,\r\n atribuicaoTiles = ATRIBUICAO_OMISSAO,\r\n truncadoEm,\r\n percurso,\r\n calor = false,\r\n pesoCalor,\r\n fundos,\r\n onSeleccionarArea,\r\n legenda,\r\n labels,\r\n className,\r\n}: MapaPontosProps<T>) {\r\n const txt = { ...LABELS_POR_OMISSAO, ...labels }\r\n const divRef = useRef<HTMLDivElement | null>(null)\r\n const mapaRef = useRef<any>(null)\r\n const camadaRef = useRef<any>(null)\r\n const tilesRef = useRef<any>(null)\r\n /** Os limites dos pontos, para reenquadrar quando o contentor mudar de tamanho. */\r\n const limitesRef = useRef<any>(null)\r\n /** O utilizador já mexeu no mapa? A partir daí a vista é dele. */\r\n const mexidoRef = useRef(false)\r\n /**\r\n * O conjunto de pontos que já foi enquadrado.\r\n *\r\n * O efeito do desenho depende das funções de aspecto (`rotulo`, `fatias`,\r\n * `forma`, `contorno`), que quem usa passa quase sempre inline — e portanto\r\n * mudam de identidade a CADA render do ecrã. Sem esta memória, qualquer\r\n * re-render reenquadrava o mapa: abrir e fechar a ficha de uma ordem\r\n * atirava o zoom de volta para o país inteiro.\r\n */\r\n const chaveEnquadradaRef = useRef<string | null>(null)\r\n\r\n /**\r\n * Um movimento nosso está a decorrer.\r\n *\r\n * O Leaflet dispara `zoomstart` tanto para o gesto da pessoa como para um\r\n * `fitBounds` nosso, e não há como os distinguir pelo evento. Sem esta\r\n * marca, o primeiro enquadramento marcava o mapa como \"já mexido\" e\r\n * bloqueava todos os seguintes.\r\n *\r\n * Dava isto, dentro de um modal: o mapa nasce com altura zero, o\r\n * `fitBounds` calcula contra 0x0 e cai no zoom mínimo, o contentor ganha\r\n * altura logo a seguir — e o reenquadramento que arrumava tudo já estava\r\n * proibido. Ficava o país inteiro.\r\n */\r\n const nossoMovimentoRef = useRef(false)\r\n\r\n /// O valor de `truncadoEm` que a pessoa já dispensou. Guardado o número e\r\n /// não um booleano para o aviso voltar quando o conjunto muda.\r\n const [avisoFechadoEm, setAvisoFechadoEm] = useState<number | null>(null)\r\n const [pronto, setPronto] = useState(false)\r\n const [fundo, setFundo] = useState(fundos?.[0]?.id ?? '')\r\n\r\n /**\r\n * Enquadrar os pontos, sem que isso conte como gesto da pessoa.\r\n *\r\n * A marca cai no `moveend`, que o Leaflet dispara no fim da animação —\r\n * incluindo quando não há animação nenhuma.\r\n */\r\n const enquadrar = useCallback((mapa: L.Map, limites: L.LatLngBounds): void => {\r\n nossoMovimentoRef.current = true\r\n mapa.once('moveend', () => { nossoMovimentoRef.current = false })\r\n mapa.fitBounds(limites, { padding: [24, 24], maxZoom: 14 })\r\n }, [])\r\n\r\n // Criação do mapa — uma só vez.\r\n useEffect(() => {\r\n if (!divRef.current || mapaRef.current) return\r\n\r\n mapaRef.current = L.map(divRef.current, {\r\n center: centro ?? CENTRO_OMISSAO,\r\n zoom,\r\n // Scroll da página não deve dar zoom sem querer; exige Ctrl.\r\n scrollWheelZoom: 'center',\r\n })\r\n const inicial = fundos?.[0]\r\n tilesRef.current = L.tileLayer(inicial?.url ?? urlTiles, {\r\n attribution: inicial?.atribuicao ?? atribuicaoTiles,\r\n maxZoom: 19,\r\n ...(inicial?.tamanhoTile ? { tileSize: inicial.tamanhoTile } : {}),\r\n ...(inicial?.deslocamentoZoom != null ? { zoomOffset: inicial.deslocamentoZoom } : {}),\r\n }).addTo(mapaRef.current)\r\n garantirVisivel(tilesRef.current)\r\n\r\n setPronto(true)\r\n\r\n /*\r\n O Leaflet mede o contentor UMA vez, na criação, e desenha para essa\r\n medida. Quando o mapa nasce antes de o layout assentar — uma coluna que\r\n ainda não tem largura, um painel lateral que abre — fica com metade da\r\n área a cinzento e o conteúdo encostado a um canto, e só se vê tudo\r\n tirando zoom à mão.\r\n\r\n O observador remedia-o a cada mudança de tamanho: apanha o primeiro\r\n assentar do layout, o abrir e fechar dos painéis e o redimensionar da\r\n janela, sem ninguém medir nada.\r\n */\r\n const observador = new ResizeObserver(() => {\r\n mapaRef.current?.invalidateSize()\r\n // Reenquadrar enquanto o utilizador não tiver mexido no mapa: depois\r\n // disso, a vista é dele e mexer-lhe seria roubar-lhe o sítio.\r\n if (!mexidoRef.current && limitesRef.current?.isValid() && mapaRef.current) {\r\n enquadrar(mapaRef.current, limitesRef.current)\r\n }\r\n })\r\n observador.observe(divRef.current)\r\n\r\n // Qualquer gesto do utilizador congela o reenquadramento automático.\r\n const marcarMexido = (): void => {\r\n if (nossoMovimentoRef.current) return\r\n mexidoRef.current = true\r\n }\r\n mapaRef.current.on('zoomstart dragstart', marcarMexido)\r\n\r\n return () => {\r\n observador.disconnect()\r\n mapaRef.current?.remove()\r\n mapaRef.current = null\r\n }\r\n }, [])\r\n\r\n /**\r\n * Selecção por caixa.\r\n *\r\n * O `boxZoom` do Leaflet usa o mesmo gesto (Shift + arrastar) e tem de ser\r\n * desligado, senão o mapa dá zoom à caixa ao mesmo tempo que se selecciona.\r\n */\r\n useEffect(() => {\r\n const mapa = mapaRef.current\r\n if (!mapa) return\r\n\r\n if (!onSeleccionarArea) {\r\n mapa.boxZoom?.enable()\r\n return\r\n }\r\n mapa.boxZoom?.disable()\r\n\r\n let inicio: L.LatLng | null = null\r\n let caixa: HTMLDivElement | null = null\r\n\r\n const posicionar = (a: L.Point, b: L.Point): void => {\r\n if (!caixa) return\r\n caixa.style.left = `${Math.min(a.x, b.x)}px`\r\n caixa.style.top = `${Math.min(a.y, b.y)}px`\r\n caixa.style.width = `${Math.abs(a.x - b.x)}px`\r\n caixa.style.height = `${Math.abs(a.y - b.y)}px`\r\n }\r\n\r\n const aoPremir = (e: L.LeafletMouseEvent): void => {\r\n const original = e.originalEvent as MouseEvent\r\n if (!original.shiftKey) return\r\n inicio = e.latlng\r\n mapa.dragging.disable()\r\n caixa = L.DomUtil.create('div', '', mapa.getPanes().overlayPane) as HTMLDivElement\r\n caixa.style.cssText =\r\n 'position:absolute;border:2px dashed #1F4E79;background:rgba(31,78,121,.12);pointer-events:none;z-index:600'\r\n posicionar(mapa.latLngToLayerPoint(inicio), mapa.latLngToLayerPoint(inicio))\r\n }\r\n\r\n const aoMover = (e: L.LeafletMouseEvent): void => {\r\n if (!inicio || !caixa) return\r\n posicionar(mapa.latLngToLayerPoint(inicio), mapa.latLngToLayerPoint(e.latlng))\r\n }\r\n\r\n const aoLargar = (e: L.LeafletMouseEvent): void => {\r\n if (!inicio) return\r\n const limites = L.latLngBounds(inicio, e.latlng)\r\n caixa?.remove()\r\n caixa = null\r\n inicio = null\r\n mapa.dragging.enable()\r\n // Um clique com Shift não é uma selecção: a caixa teria área zero e\r\n // devolveria lista vazia, o que se leria como \"não há nada aqui\".\r\n if (limites.getNorth() === limites.getSouth()) return\r\n onSeleccionarArea(pontos.filter((p) => limites.contains([p.lat, p.lng])))\r\n }\r\n\r\n mapa.on('mousedown', aoPremir)\r\n mapa.on('mousemove', aoMover)\r\n mapa.on('mouseup', aoLargar)\r\n\r\n return () => {\r\n mapa.off('mousedown', aoPremir)\r\n mapa.off('mousemove', aoMover)\r\n mapa.off('mouseup', aoLargar)\r\n caixa?.remove()\r\n mapa.dragging.enable()\r\n }\r\n }, [pronto, onSeleccionarArea, pontos])\r\n\r\n /*\r\n Troca do fundo.\r\n\r\n Substitui a camada em vez de acrescentar outra por cima — empilhadas, a de\r\n baixo continua a pedir tiles que ninguém vê.\r\n\r\n Duas regras que não são detalhe:\r\n\r\n 1. NÃO mexer quando o fundo já é o que está no mapa. A camada inicial é\r\n criada ao nascer o mapa; se este efeito voltasse a removê-la e a\r\n recriá-la na primeira passagem, estaria a desfazer trabalho acabado de\r\n fazer — e era aí que tudo corria mal.\r\n\r\n 2. A nova camada entra ANTES de a velha sair, e o `invalidateSize` fica\r\n para o quadro seguinte. Remover e recriar no mesmo tique, e logo a\r\n seguir forçar um recálculo de tamanho, apanha o Leaflet a meio da\r\n animação de aparecimento: os tiles chegam do servidor, são inseridos no\r\n DOM e ficam com opacidade 0 para sempre. O mapa fica branco com os\r\n marcadores por cima, e a consola não diz nada porque nada falhou.\r\n */\r\n const fundoAplicadoRef = useRef<string | null>(null)\r\n\r\n useEffect(() => {\r\n const mapa = mapaRef.current\r\n const escolhido = fundos?.find((f) => f.id === fundo)\r\n if (!mapa || !escolhido || !tilesRef.current) return\r\n if (fundoAplicadoRef.current === escolhido.id) return\r\n if (fundoAplicadoRef.current === null) {\r\n // A camada inicial já é esta. Marca-se como aplicada e não se lhe toca.\r\n fundoAplicadoRef.current = escolhido.id\r\n return\r\n }\r\n\r\n /*\r\n Guardar a vista antes de mexer nas camadas: fundos com tamanhos de tile\r\n diferentes (256 e 512) obrigam o Leaflet a recalcular a escala, e entre\r\n uma camada e outra o mapa fica sem referência — era o que fazia os\r\n marcadores saltarem para fora do contentor.\r\n */\r\n const centroActual = mapa.getCenter()\r\n const zoomActual = mapa.getZoom()\r\n const anterior = tilesRef.current\r\n\r\n const nova = L.tileLayer(escolhido.url, {\r\n attribution: escolhido.atribuicao ?? atribuicaoTiles,\r\n maxZoom: 19,\r\n ...(escolhido.tamanhoTile ? { tileSize: escolhido.tamanhoTile } : {}),\r\n ...(escolhido.deslocamentoZoom != null ? { zoomOffset: escolhido.deslocamentoZoom } : {}),\r\n }).addTo(mapa)\r\n // Os tiles pertencem ao painel de baixo; sem isto ficavam por cima dos\r\n // marcadores depois de uma troca.\r\n nova.bringToBack()\r\n garantirVisivel(nova)\r\n\r\n tilesRef.current = nova\r\n fundoAplicadoRef.current = escolhido.id\r\n anterior.remove()\r\n\r\n mapa.setView(centroActual, zoomActual, { animate: false })\r\n requestAnimationFrame(() => mapaRef.current?.invalidateSize())\r\n }, [pronto, fundo])\r\n\r\n /*\r\n O traçado.\r\n\r\n Camada própria e refeita quando o percurso muda, em vez de ir junto com os\r\n marcadores: os pontos redesenham-se a cada filtro e o traçado não tem de\r\n acompanhar esse ritmo.\r\n\r\n Vai para trás dos marcadores — uma linha por cima dos números tapava\r\n justamente o que ela serve para ligar.\r\n */\r\n /*\r\n O mapa de calor.\r\n\r\n Em camada própria e em efeito próprio: liga-se e desliga-se muitas vezes, e\r\n pendurá-lo no desenho dos marcadores obrigava a refazer dez mil marcadores\r\n de cada vez que alguém carregava na caixa.\r\n\r\n Fica POR BAIXO dos marcadores. Por cima, a mancha tapava justamente os\r\n pontos que ela serve para situar.\r\n */\r\n /** Identidade do conjunto de pontos, para não redesenhar o que não mudou. */\r\n const chavePontos = useMemo(() => `${pontos.length}:${pontos[0]?.id ?? ''}:${pontos[pontos.length - 1]?.id ?? ''}`, [pontos])\r\n\r\n const calorRef = useRef<any>(null)\r\n\r\n useEffect(() => {\r\n const mapa = mapaRef.current\r\n if (!mapa) return\r\n\r\n calorRef.current?.remove()\r\n calorRef.current = null\r\n if (!calor) return\r\n\r\n const dados = pontos\r\n .filter((p) => Number.isFinite(p.lat) && Number.isFinite(p.lng))\r\n .map((p) => [p.lat, p.lng, pesoCalor?.(p) ?? 1] as [number, number, number])\r\n if (!dados.length) return\r\n\r\n const heat = (L as any).heatLayer\r\n // Sem o plugin, não se desenha nada em vez de rebentar: o mapa continua a\r\n // servir para tudo o resto.\r\n if (typeof heat !== 'function') return\r\n\r\n calorRef.current = heat(dados, {\r\n radius: 22,\r\n blur: 18,\r\n // Sem tecto, um concelho com 2 000 ordens saturava o país inteiro a\r\n // vermelho e a mancha deixava de distinguir fosse o que fosse.\r\n max: Math.max(1, ...dados.map((d) => d[2])),\r\n minOpacity: 0.25,\r\n })\r\n calorRef.current.addTo(mapa)\r\n // A camada do calor nasce no painel dos overlays, que fica acima dos\r\n // marcadores; desce-se para os pontos continuarem clicáveis e visíveis.\r\n calorRef.current.getPane?.()\r\n const el = calorRef.current._canvas as HTMLElement | undefined\r\n if (el) el.style.zIndex = '350'\r\n\r\n return () => {\r\n calorRef.current?.remove()\r\n calorRef.current = null\r\n }\r\n }, [pronto, calor, chavePontos, pesoCalor])\r\n\r\n const percursoRef = useRef<any>(null)\r\n\r\n useEffect(() => {\r\n const mapa = mapaRef.current\r\n if (!mapa) return\r\n\r\n percursoRef.current?.remove()\r\n percursoRef.current = null\r\n if (!percurso?.length) return\r\n\r\n percursoRef.current = L.polyline(percurso, {\r\n color: '#1F4E79',\r\n weight: 3,\r\n opacity: 0.75,\r\n // Tracejado: lembra que é um percurso proposto, não um registo do que\r\n // foi feito.\r\n dashArray: '6 6',\r\n }).addTo(mapa)\r\n percursoRef.current.bringToBack()\r\n tilesRef.current?.bringToBack()\r\n }, [pronto, percurso])\r\n\r\n\r\n // Desenho dos pontos — refeito quando a lista muda.\r\n useEffect(() => {\r\n const mapa = mapaRef.current\r\n if (!mapa) return\r\n\r\n camadaRef.current?.remove()\r\n\r\n /**\r\n * Ícone do grupo: o mesmo anel dos marcadores, com a composição somada.\r\n *\r\n * Fechar pontos não pode apagar o que eles diziam — o grupo azul por\r\n * omissão do markercluster diz quantos, e nada sobre o que são.\r\n */\r\n const iconeGrupo = (c: any) => {\r\n const filhos: T[] = c.getAllChildMarkers()\r\n .map((mk: any) => mk.options.ponto)\r\n .filter(Boolean)\r\n const soma = new Map<string, FatiaMapa>()\r\n for (const f of filhos.flatMap((p) => fatias?.(p) ?? [{ cor: cor?.(p) ?? COR_OMISSAO, n: 1 }])) {\r\n const anterior = soma.get(f.cor)\r\n if (anterior) anterior.n += f.n\r\n else soma.set(f.cor, { ...f })\r\n }\r\n // Cada filho conta o que as suas fatias somam; sem fatias, conta por um.\r\n const total = filhos.length\r\n ? filhos.reduce((n, p) => n + ((fatias?.(p) ?? []).reduce((x, f) => x + f.n, 0) || 1), 0)\r\n : c.getChildCount()\r\n const lado = total < 10 ? 26 : total < 100 ? 32 : total < 1000 ? 38 : 46\r\n return L.divIcon({\r\n className: '',\r\n iconSize: [lado, lado],\r\n html: anelHtml([...soma.values()], total.toLocaleString('pt-PT'), lado),\r\n })\r\n }\r\n\r\n const camada = agrupar && (L as any).markerClusterGroup\r\n ? (L as any).markerClusterGroup({\r\n chunkedLoading: true,\r\n maxClusterRadius: 50,\r\n showCoverageOnHover: false,\r\n // Sem isto, marcadores na MESMA coordenada nunca se separam por muito\r\n // zoom que se faça, e os de baixo ficam inalcançáveis.\r\n spiderfyOnMaxZoom: true,\r\n ...(forma === 'anel' || fatias ? { iconCreateFunction: iconeGrupo } : {}),\r\n })\r\n : L.layerGroup()\r\n\r\n for (const p of pontos) {\r\n if (!Number.isFinite(p.lat) || !Number.isFinite(p.lng)) continue\r\n const estimado = aproximado?.(p) ?? false\r\n\r\n if (forma === 'anel') {\r\n const composicao = fatias?.(p) ?? [{ cor: cor?.(p) ?? COR_OMISSAO, n: 1 }]\r\n // Diâmetro: `raio` continua a ser o raio, para o `raio` dos outros\r\n // modos querer dizer a mesma coisa aqui.\r\n const lado = Math.round((raio?.(p) ?? 18) * 2)\r\n const marcadorAnel = L.marker([p.lat, p.lng], {\r\n icon: L.divIcon({\r\n className: '',\r\n iconSize: [lado, lado],\r\n iconAnchor: [lado / 2, lado / 2],\r\n popupAnchor: [0, -lado / 2],\r\n html: anelHtml(composicao, rotulo?.(p) ?? '', lado, selo?.(p), contorno?.(p)),\r\n }),\r\n })\r\n const htmlAnel = balao?.(p)\r\n if (htmlAnel) marcadorAnel.bindPopup(htmlAnel)\r\n if (onPontoClick) marcadorAnel.on('click', () => onPontoClick(p))\r\n ;(marcadorAnel.options as { ponto?: T }).ponto = p\r\n camada.addLayer(marcadorAnel)\r\n continue\r\n }\r\n\r\n const marcador = forma === 'pin'\r\n ? L.marker([p.lat, p.lng], {\r\n icon: L.divIcon({\r\n className: '',\r\n // A ponta do pin é que assenta na coordenada, não o centro do\r\n // ícone — daí a âncora no fundo, a meio da largura.\r\n iconSize: [TAMANHO_PIN, TAMANHO_PIN],\r\n iconAnchor: [TAMANHO_PIN / 2, TAMANHO_PIN],\r\n popupAnchor: [0, -TAMANHO_PIN + 4],\r\n html: pinSvg(cor?.(p) ?? COR_OMISSAO, estimado),\r\n }),\r\n })\r\n : L.circleMarker([p.lat, p.lng], {\r\n radius: raio?.(p) ?? 6,\r\n color: cor?.(p) ?? COR_OMISSAO,\r\n weight: estimado ? 1 : 2,\r\n // Tracejado e mais transparente: a posição é estimada, e o mapa não\r\n // deve sugerir uma precisão que não tem.\r\n dashArray: estimado ? '2,3' : undefined,\r\n fillOpacity: estimado ? 0.25 : 0.75,\r\n fillColor: cor?.(p) ?? COR_OMISSAO,\r\n })\r\n ;(marcador.options as { ponto?: T }).ponto = p\r\n const html = balao?.(p)\r\n if (html) marcador.bindPopup(html)\r\n if (onPontoClick) marcador.on('click', () => onPontoClick(p))\r\n camada.addLayer(marcador)\r\n\r\n const texto = rotulo?.(p)\r\n if (texto) {\r\n // `interactive: false` para o rótulo não roubar o clique ao círculo\r\n // que está por baixo — senão clicar no número não faz nada.\r\n camada.addLayer(\r\n L.marker([p.lat, p.lng], {\r\n interactive: false,\r\n icon: L.divIcon({\r\n className: '',\r\n iconSize: [0, 0],\r\n html:\r\n `<div style=\"transform:translate(-50%,-50%);white-space:nowrap;font:700 11px/1 system-ui,sans-serif;` +\r\n `color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.7);pointer-events:none\">${texto}</div>`,\r\n }),\r\n })\r\n )\r\n }\r\n }\r\n\r\n camada.addTo(mapa)\r\n camadaRef.current = camada\r\n\r\n if (pontos.length) {\r\n const limites = L.latLngBounds(pontos.map((p) => [p.lat, p.lng]))\r\n limitesRef.current = limites.isValid() ? limites : null\r\n /*\r\n Pontos novos são um conjunto novo: vale a pena voltar a enquadrar,\r\n mesmo que o utilizador já tenha mexido — senão um filtro que reduz a\r\n 3 pontos deixava o mapa onde estava, aparentemente vazio.\r\n\r\n Mas só quando o CONJUNTO muda. Um re-render que apenas trocou a\r\n identidade de uma função de aspecto não é um conjunto novo, e tratá-lo\r\n como tal desfazia o zoom que a pessoa tinha acabado de dar.\r\n */\r\n if (limitesRef.current && !centro && chaveEnquadradaRef.current !== chavePontos) {\r\n chaveEnquadradaRef.current = chavePontos\r\n mexidoRef.current = false\r\n enquadrar(mapa, limitesRef.current)\r\n }\r\n }\r\n }, [pronto, chavePontos, agrupar, rotulo, forma, fatias, contorno])\r\n\r\n // Dessaturar o fundo? Só no tema suave, e nunca num satélite.\r\n const suavizar = tema === 'suave' && !fundos?.find((f) => f.id === fundo)?.semFiltro\r\n\r\n return (\r\n /*\r\n `overflow-hidden` e `isolate` não são decoração.\r\n\r\n O CORTE: os painéis do Leaflet são absolutos e deslocados por\r\n transformações. Se o mapa ficar com uma posição interna inconsistente —\r\n ao trocar de fundo, por exemplo — os marcadores pintam-se por cima do\r\n resto da página. Aconteceu. O corte garante que o pior caso é um mapa\r\n vazio, não a página desfeita.\r\n\r\n O ISOLAMENTO: o Leaflet dá z-index 1000 aos seus controlos e 400 aos\r\n painéis. `relative` sozinho NÃO cria contexto de empilhamento — só o cria\r\n com um `z-index` — por isso esses valores subiam até à raiz da página e\r\n competiam de igual para igual com tudo o resto. Um modal a z-50 ficava por\r\n baixo do mapa: abria-se uma ordem e o mapa pintava-se por cima da ficha.\r\n\r\n `isolate` fecha-os cá dentro. O mapa passa a valer como um elemento só na\r\n ordem de empilhamento da página, e deixa de haver maneira de uma peça\r\n interna dele passar à frente de um modal, de um menu ou de um tooltip.\r\n */\r\n <div\r\n className={`relative isolate overflow-hidden ${suavizar ? 'mapa-suave' : ''} ${className ?? ''}`}\r\n style={{ height: altura }}\r\n >\r\n {/* O filtro aplica-se SÓ ao painel dos tiles: os marcadores vivem em\r\n painéis acima e mantêm a cor. */}\r\n {suavizar && (\r\n <style>{`.mapa-suave .leaflet-tile-pane{filter:grayscale(.85) brightness(1.06) contrast(.95)}`}</style>\r\n )}\r\n {/*\r\n O contentor do Leaflet NÃO leva `className` do React.\r\n\r\n O Leaflet escreve as classes dele neste mesmo elemento\r\n (`leaflet-container`, `leaflet-grab`, `leaflet-fade-anim`…). O React\r\n controla o atributo `className` inteiro: bastava a condição mudar — ao\r\n trocar para um fundo de satélite, que dispensa a dessaturação — para\r\n ele reescrever o atributo e varrer as classes do Leaflet.\r\n\r\n Sem `leaflet-container`, os painéis perdem o posicionamento e o mapa\r\n fica branco com os marcadores por cima. E não voltava: ao trocar de\r\n novo, o React só reescrevia outra vez.\r\n\r\n A classe do filtro vive no invólucro, que é do React e mais ninguém\r\n lhe toca.\r\n */}\r\n <div ref={divRef} style={{ height: '100%', width: '100%' }} aria-label=\"Mapa\" />\r\n\r\n {pronto && !pontos.length && (\r\n <div className=\"absolute inset-0 flex items-center justify-center text-sm text-slate-500 pointer-events-none\">\r\n {txt.semPontos}\r\n </div>\r\n )}\r\n\r\n {!!fundos?.length && (\r\n <div className=\"absolute top-2 left-12 z-[400] bg-white/90 dark:bg-slate-900/90 backdrop-blur-sm shadow-sm rounded flex text-[11px] overflow-hidden\">\r\n {fundos.map((f) => (\r\n <button\r\n key={f.id}\r\n onClick={() => setFundo(f.id)}\r\n className={`px-1.5 py-0.5 ${\r\n fundo === f.id ? 'bg-proef-navy dark:bg-slate-700 text-white font-semibold' : 'text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800'\r\n }`}\r\n >\r\n {f.rotulo}\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n\r\n {/* Compacta e encostada: a legenda explica o mapa, não o tapa. */}\r\n {legenda && (\r\n <div className=\"absolute bottom-2 left-2 z-[400] bg-white/90 dark:bg-slate-900/90 dark:text-slate-200 backdrop-blur-sm rounded shadow-sm px-2 py-1 text-[11px] leading-tight\">\r\n {legenda}\r\n </div>\r\n )}\r\n\r\n {/*\r\n O aviso de truncagem fecha-se.\r\n\r\n Diz uma coisa importante na primeira vez — que o mapa não está a\r\n mostrar tudo — e nada de novo das vezes seguintes. Enquanto não se\r\n podia fechar, tapava o canto do mapa durante toda a sessão.\r\n\r\n Volta a aparecer quando o número muda: aí é um aviso diferente, sobre\r\n um conjunto diferente.\r\n */}\r\n {!!truncadoEm && txt.truncado && avisoFechadoEm !== truncadoEm && (\r\n <div className=\"absolute top-3 right-3 z-[400] flex items-start gap-2 bg-amber-50 border border-amber-300 text-amber-900 shadow rounded-lg px-2.5 py-1 text-[11px] max-w-[22rem]\">\r\n <span>{txt.truncado.replace('{n}', String(truncadoEm))}</span>\r\n <button\r\n onClick={() => setAvisoFechadoEm(truncadoEm)}\r\n aria-label={txt.fecharAviso ?? 'Fechar'}\r\n title={txt.fecharAviso ?? 'Fechar'}\r\n className=\"text-amber-700 hover:text-amber-900 leading-none font-bold\"\r\n >\r\n ×\r\n </button>\r\n </div>\r\n )}\r\n </div>\r\n )\r\n}\r\n"],"names":[],"mappings":";;;;;AAkDA,SAAS,gBAAgB,QAAmB;AAC1C,QAAM,QAAQ,MAAY,OAAO,aAAa,CAAC;AAC/C,SAAO,GAAG,QAAQ,KAAK;AACvB,QAAA;AACF;AA6CA,MAAM,qBAAuC;AAAA,EAC3C,WAAW;AAAA,EACX,eAAe;AAAA,EACf,WAAW;AAAA,EACX,UAAU;AAAA,EACV,aAAa;AACf;AAyJA,MAAM,gBAAgB;AACtB,MAAM,qBAAqB;AAG3B,MAAM,iBAAmC,CAAC,MAAM,EAAI;AAEpD,MAAM,cAAc;AACpB,MAAM,cAAc;AAUpB,SAAS,OAAO,KAAa,YAA6B;AACxD,SAAO,kDAAkD,WAAW,aAAa,WAAW;AAAA,mEAC3B,aAAa,OAAO,CAAC;AAAA;AAAA,cAE1E,GAAG,wCAAwC,aAAa,8BAA8B,EAAE;AAAA;AAAA;AAGtG;AAGA,SAAS,QAAQ,OAAuB;AACtC,SAAO,MAAM,QAAQ,YAAY,CAAC,OAC/B,EAAE,KAAK,SAAS,KAAK,QAAQ,KAAK,QAAQ,KAAK,UAAU,KAAK,QAAA,GAAW,CAAC,CAAW;AAC1F;AASA,SAAS,QAAQ,QAA6B;AAC5C,QAAM,WAAW,OAAO,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC;AAC7C,MAAI,CAAC,SAAS,OAAQ,QAAO;AAE7B,MAAI,SAAS,WAAW,EAAG,QAAO,SAAS,CAAC,EAAG;AAC/C,QAAM,QAAQ,SAAS,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,GAAG,CAAC;AAClD,MAAI,MAAM;AACV,QAAM,SAAS,SAAS,IAAI,CAAC,MAAM;AACjC,UAAM,MAAO,MAAM,QAAS;AAC5B,WAAO,EAAE;AACT,WAAO,GAAG,EAAE,GAAG,IAAI,IAAI,QAAQ,CAAC,CAAC,MAAO,MAAM,QAAS,KAAK,QAAQ,CAAC,CAAC;AAAA,EACxE,CAAC;AACD,SAAO,kBAAkB,OAAO,KAAK,GAAG,CAAC;AAC3C;AAQA,SAAS,SACP,QACA,OACA,MACA,MACA,UACQ;AAER,QAAM,YAAY,OAAO,KAAK,IAAI,OAAO,KAAK,IAAI;AAClD,QAAM,SAAS,OACZ,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,EACrB,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC,GAAG,EAAE,SAAS,IAAI,EAAE,MAAM,KAAK,EAAE,EAAE,EACpD,KAAK,KAAK;AACb,QAAM,IAAI,KAAK,MAAM,OAAO,GAAI;AAChC,QAAM,QAAQ,OACV,eAAe,QAAQ,KAAK,UAAU,KAAK,KAAK,CAAC,kCAAkC,CAAC,IAAI,IAAI,YAAY,CAAC,IAAI,IAAI,YACxG,CAAC,aAAa,CAAC,sCAAsC,KAAK,GAAG,4IAE/C,KAAK,MAAM,IAAI,IAAI,CAAC,8BAA8B,QAAQ,KAAK,KAAK,CAAC,WAC5F;AACJ,QAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,MAAM,SAAS,IAAI,OAAO,KAAK,CAAC;AAG7E,QAAM,QAAQ,WAAW,gBAAgB,QAAQ,KAAK;AACtD,SAAO,eAAe,QAAQ,MAAM,CAAC,kBAAkB,IAAI,aAAa,IAAI,sCAC5D,QAAQ,MAAM,CAAC,uCAAuC,KAAK,2DAClC,SAAS,yHAEpC,KAAK,8BAA8B,QAAQ,KAAK,CAAC,SAAS,KAAK;AAC/E;AAGA,SAAwB,WAAgC;AAAA,EACtD;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,SAAS;AAAA,EACT,UAAU;AAAA,EACV,OAAO;AAAA,EACP,WAAW;AAAA,EACX,kBAAkB;AAAA,EAClB;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,MAAM,EAAE,GAAG,oBAAoB,GAAG,OAAA;AACxC,QAAM,SAAS,OAA8B,IAAI;AACjD,QAAM,UAAU,OAAY,IAAI;AAChC,QAAM,YAAY,OAAY,IAAI;AAClC,QAAM,WAAW,OAAY,IAAI;AAEjC,QAAM,aAAa,OAAY,IAAI;AAEnC,QAAM,YAAY,OAAO,KAAK;AAU9B,QAAM,qBAAqB,OAAsB,IAAI;AAerD,QAAM,oBAAoB,OAAO,KAAK;AAItC,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAwB,IAAI;AACxE,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAC1C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,SAAS,CAAC,GAAG,MAAM,EAAE;AAQxD,QAAM,YAAY,YAAY,CAAC,MAAa,YAAkC;AAC5E,sBAAkB,UAAU;AAC5B,SAAK,KAAK,WAAW,MAAM;AAAE,wBAAkB,UAAU;AAAA,IAAM,CAAC;AAChE,SAAK,UAAU,SAAS,EAAE,SAAS,CAAC,IAAI,EAAE,GAAG,SAAS,IAAI;AAAA,EAC5D,GAAG,CAAA,CAAE;AAGL,YAAU,MAAM;AACd,QAAI,CAAC,OAAO,WAAW,QAAQ,QAAS;AAExC,YAAQ,UAAU,EAAE,IAAI,OAAO,SAAS;AAAA,MACtC,QAAQ,UAAU;AAAA,MAClB;AAAA;AAAA,MAEA,iBAAiB;AAAA,IAAA,CAClB;AACD,UAAM,UAAU,SAAS,CAAC;AAC1B,aAAS,UAAU,EAAE,UAAU,SAAS,OAAO,UAAU;AAAA,MACvD,aAAa,SAAS,cAAc;AAAA,MACpC,SAAS;AAAA,MACT,GAAI,SAAS,cAAc,EAAE,UAAU,QAAQ,YAAA,IAAgB,CAAA;AAAA,MAC/D,GAAI,SAAS,oBAAoB,OAAO,EAAE,YAAY,QAAQ,qBAAqB,CAAA;AAAA,IAAC,CACrF,EAAE,MAAM,QAAQ,OAAO;AACxB,oBAAgB,SAAS,OAAO;AAEhC,cAAU,IAAI;AAad,UAAM,aAAa,IAAI,eAAe,MAAM;AAC1C,cAAQ,SAAS,eAAA;AAGjB,UAAI,CAAC,UAAU,WAAW,WAAW,SAAS,QAAA,KAAa,QAAQ,SAAS;AAC1E,kBAAU,QAAQ,SAAS,WAAW,OAAO;AAAA,MAC/C;AAAA,IACF,CAAC;AACD,eAAW,QAAQ,OAAO,OAAO;AAGjC,UAAM,eAAe,MAAY;AAC/B,UAAI,kBAAkB,QAAS;AAC/B,gBAAU,UAAU;AAAA,IACtB;AACA,YAAQ,QAAQ,GAAG,uBAAuB,YAAY;AAEtD,WAAO,MAAM;AACX,iBAAW,WAAA;AACX,cAAQ,SAAS,OAAA;AACjB,cAAQ,UAAU;AAAA,IACpB;AAAA,EACF,GAAG,CAAA,CAAE;AAQL,YAAU,MAAM;AACd,UAAM,OAAO,QAAQ;AACrB,QAAI,CAAC,KAAM;AAEX,QAAI,CAAC,mBAAmB;AACtB,WAAK,SAAS,OAAA;AACd;AAAA,IACF;AACA,SAAK,SAAS,QAAA;AAEd,QAAI,SAA0B;AAC9B,QAAI,QAA+B;AAEnC,UAAM,aAAa,CAAC,GAAY,MAAqB;AACnD,UAAI,CAAC,MAAO;AACZ,YAAM,MAAM,OAAO,GAAG,KAAK,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;AACxC,YAAM,MAAM,MAAM,GAAG,KAAK,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;AACvC,YAAM,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AAC1C,YAAM,MAAM,SAAS,GAAG,KAAK,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AAAA,IAC7C;AAEA,UAAM,WAAW,CAAC,MAAiC;AACjD,YAAM,WAAW,EAAE;AACnB,UAAI,CAAC,SAAS,SAAU;AACxB,eAAS,EAAE;AACX,WAAK,SAAS,QAAA;AACd,cAAQ,EAAE,QAAQ,OAAO,OAAO,IAAI,KAAK,SAAA,EAAW,WAAW;AAC/D,YAAM,MAAM,UACV;AACF,iBAAW,KAAK,mBAAmB,MAAM,GAAG,KAAK,mBAAmB,MAAM,CAAC;AAAA,IAC7E;AAEA,UAAM,UAAU,CAAC,MAAiC;AAChD,UAAI,CAAC,UAAU,CAAC,MAAO;AACvB,iBAAW,KAAK,mBAAmB,MAAM,GAAG,KAAK,mBAAmB,EAAE,MAAM,CAAC;AAAA,IAC/E;AAEA,UAAM,WAAW,CAAC,MAAiC;AACjD,UAAI,CAAC,OAAQ;AACb,YAAM,UAAU,EAAE,aAAa,QAAQ,EAAE,MAAM;AAC/C,aAAO,OAAA;AACP,cAAQ;AACR,eAAS;AACT,WAAK,SAAS,OAAA;AAGd,UAAI,QAAQ,SAAA,MAAe,QAAQ,WAAY;AAC/C,wBAAkB,OAAO,OAAO,CAAC,MAAM,QAAQ,SAAS,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;AAAA,IAC1E;AAEA,SAAK,GAAG,aAAa,QAAQ;AAC7B,SAAK,GAAG,aAAa,OAAO;AAC5B,SAAK,GAAG,WAAW,QAAQ;AAE3B,WAAO,MAAM;AACX,WAAK,IAAI,aAAa,QAAQ;AAC9B,WAAK,IAAI,aAAa,OAAO;AAC7B,WAAK,IAAI,WAAW,QAAQ;AAC5B,aAAO,OAAA;AACP,WAAK,SAAS,OAAA;AAAA,IAChB;AAAA,EACF,GAAG,CAAC,QAAQ,mBAAmB,MAAM,CAAC;AAsBtC,QAAM,mBAAmB,OAAsB,IAAI;AAEnD,YAAU,MAAM;AACd,UAAM,OAAO,QAAQ;AACrB,UAAM,YAAY,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,KAAK;AACpD,QAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,SAAS,QAAS;AAC9C,QAAI,iBAAiB,YAAY,UAAU,GAAI;AAC/C,QAAI,iBAAiB,YAAY,MAAM;AAErC,uBAAiB,UAAU,UAAU;AACrC;AAAA,IACF;AAQA,UAAM,eAAe,KAAK,UAAA;AAC1B,UAAM,aAAa,KAAK,QAAA;AACxB,UAAM,WAAW,SAAS;AAE1B,UAAM,OAAO,EAAE,UAAU,UAAU,KAAK;AAAA,MACtC,aAAa,UAAU,cAAc;AAAA,MACrC,SAAS;AAAA,MACT,GAAI,UAAU,cAAc,EAAE,UAAU,UAAU,YAAA,IAAgB,CAAA;AAAA,MAClE,GAAI,UAAU,oBAAoB,OAAO,EAAE,YAAY,UAAU,qBAAqB,CAAA;AAAA,IAAC,CACxF,EAAE,MAAM,IAAI;AAGb,SAAK,YAAA;AACL,oBAAgB,IAAI;AAEpB,aAAS,UAAU;AACnB,qBAAiB,UAAU,UAAU;AACrC,aAAS,OAAA;AAET,SAAK,QAAQ,cAAc,YAAY,EAAE,SAAS,OAAO;AACzD,0BAAsB,MAAM,QAAQ,SAAS,eAAA,CAAgB;AAAA,EAC/D,GAAG,CAAC,QAAQ,KAAK,CAAC;AAuBlB,QAAM,cAAc,QAAQ,MAAM,GAAG,OAAO,MAAM,IAAI,OAAO,CAAC,GAAG,MAAM,EAAE,IAAI,OAAO,OAAO,SAAS,CAAC,GAAG,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC;AAE5H,QAAM,WAAW,OAAY,IAAI;AAEjC,YAAU,MAAM;AACd,UAAM,OAAO,QAAQ;AACrB,QAAI,CAAC,KAAM;AAEX,aAAS,SAAS,OAAA;AAClB,aAAS,UAAU;AACnB,QAAI,CAAC,MAAO;AAEZ,UAAM,QAAQ,OACX,OAAO,CAAC,MAAM,OAAO,SAAS,EAAE,GAAG,KAAK,OAAO,SAAS,EAAE,GAAG,CAAC,EAC9D,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,KAAK,YAAY,CAAC,KAAK,CAAC,CAA6B;AAC7E,QAAI,CAAC,MAAM,OAAQ;AAEnB,UAAM,OAAQ,EAAU;AAGxB,QAAI,OAAO,SAAS,WAAY;AAEhC,aAAS,UAAU,KAAK,OAAO;AAAA,MAC7B,QAAQ;AAAA,MACR,MAAM;AAAA;AAAA;AAAA,MAGN,KAAK,KAAK,IAAI,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AAAA,MAC1C,YAAY;AAAA,IAAA,CACb;AACD,aAAS,QAAQ,MAAM,IAAI;AAG3B,aAAS,QAAQ,UAAA;AACjB,UAAM,KAAK,SAAS,QAAQ;AAC5B,QAAI,GAAI,IAAG,MAAM,SAAS;AAE1B,WAAO,MAAM;AACX,eAAS,SAAS,OAAA;AAClB,eAAS,UAAU;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,QAAQ,OAAO,aAAa,SAAS,CAAC;AAE1C,QAAM,cAAc,OAAY,IAAI;AAEpC,YAAU,MAAM;AACd,UAAM,OAAO,QAAQ;AACrB,QAAI,CAAC,KAAM;AAEX,gBAAY,SAAS,OAAA;AACrB,gBAAY,UAAU;AACtB,QAAI,CAAC,UAAU,OAAQ;AAEvB,gBAAY,UAAU,EAAE,SAAS,UAAU;AAAA,MACzC,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,SAAS;AAAA;AAAA;AAAA,MAGT,WAAW;AAAA,IAAA,CACZ,EAAE,MAAM,IAAI;AACb,gBAAY,QAAQ,YAAA;AACpB,aAAS,SAAS,YAAA;AAAA,EACpB,GAAG,CAAC,QAAQ,QAAQ,CAAC;AAIrB,YAAU,MAAM;AACd,UAAM,OAAO,QAAQ;AACrB,QAAI,CAAC,KAAM;AAEX,cAAU,SAAS,OAAA;AAQnB,UAAM,aAAa,CAAC,MAAW;AAC7B,YAAM,SAAc,EAAE,mBAAA,EACnB,IAAI,CAAC,OAAY,GAAG,QAAQ,KAAK,EACjC,OAAO,OAAO;AACjB,YAAM,2BAAW,IAAA;AACjB,iBAAW,KAAK,OAAO,QAAQ,CAAC,MAAM,SAAS,CAAC,KAAK,CAAC,EAAE,KAAK,MAAM,CAAC,KAAK,aAAa,GAAG,EAAA,CAAG,CAAC,GAAG;AAC9F,cAAM,WAAW,KAAK,IAAI,EAAE,GAAG;AAC/B,YAAI,SAAU,UAAS,KAAK,EAAE;AAAA,kBACpB,IAAI,EAAE,KAAK,EAAE,GAAG,GAAG;AAAA,MAC/B;AAEA,YAAM,QAAQ,OAAO,SACjB,OAAO,OAAO,CAAC,GAAG,MAAM,MAAM,SAAS,CAAC,KAAK,CAAA,GAAI,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,GAAG,CAAC,KAAK,IAAI,CAAC,IACtF,EAAE,cAAA;AACN,YAAM,OAAO,QAAQ,KAAK,KAAK,QAAQ,MAAM,KAAK,QAAQ,MAAO,KAAK;AACtE,aAAO,EAAE,QAAQ;AAAA,QACf,WAAW;AAAA,QACX,UAAU,CAAC,MAAM,IAAI;AAAA,QACrB,MAAM,SAAS,CAAC,GAAG,KAAK,QAAQ,GAAG,MAAM,eAAe,OAAO,GAAG,IAAI;AAAA,MAAA,CACvE;AAAA,IACH;AAEA,UAAM,SAAS,WAAY,EAAU,qBAChC,EAAU,mBAAmB;AAAA,MAC5B,gBAAgB;AAAA,MAChB,kBAAkB;AAAA,MAClB,qBAAqB;AAAA;AAAA;AAAA,MAGrB,mBAAmB;AAAA,MACnB,GAAI,UAAU,UAAU,SAAS,EAAE,oBAAoB,WAAA,IAAe,CAAA;AAAA,IAAC,CACxE,IACD,EAAE,WAAA;AAEN,eAAW,KAAK,QAAQ;AACtB,UAAI,CAAC,OAAO,SAAS,EAAE,GAAG,KAAK,CAAC,OAAO,SAAS,EAAE,GAAG,EAAG;AACxD,YAAM,WAAW,aAAa,CAAC,KAAK;AAEpC,UAAI,UAAU,QAAQ;AACpB,cAAM,aAAa,SAAS,CAAC,KAAK,CAAC,EAAE,KAAK,MAAM,CAAC,KAAK,aAAa,GAAG,GAAG;AAGzE,cAAM,OAAO,KAAK,OAAO,OAAO,CAAC,KAAK,MAAM,CAAC;AAC7C,cAAM,eAAe,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,GAAG,GAAG;AAAA,UAC5C,MAAM,EAAE,QAAQ;AAAA,YACd,WAAW;AAAA,YACX,UAAU,CAAC,MAAM,IAAI;AAAA,YACrB,YAAY,CAAC,OAAO,GAAG,OAAO,CAAC;AAAA,YAC/B,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC;AAAA,YAC1B,MAAM,SAAS,YAAY,SAAS,CAAC,KAAK,IAAI,MAAM,OAAO,CAAC,GAAG,WAAW,CAAC,CAAC;AAAA,UAAA,CAC7E;AAAA,QAAA,CACF;AACD,cAAM,WAAW,QAAQ,CAAC;AAC1B,YAAI,SAAU,cAAa,UAAU,QAAQ;AAC7C,YAAI,aAAc,cAAa,GAAG,SAAS,MAAM,aAAa,CAAC,CAAC;AAC9D,qBAAa,QAA0B,QAAQ;AACjD,eAAO,SAAS,YAAY;AAC5B;AAAA,MACF;AAEA,YAAM,WAAW,UAAU,QACvB,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,GAAG,GAAG;AAAA,QACvB,MAAM,EAAE,QAAQ;AAAA,UACd,WAAW;AAAA;AAAA;AAAA,UAGX,UAAU,CAAC,aAAa,WAAW;AAAA,UACnC,YAAY,CAAC,cAAc,GAAG,WAAW;AAAA,UACzC,aAAa,CAAC,GAAG,CAAC,cAAc,CAAC;AAAA,UACjC,MAAM,OAAO,MAAM,CAAC,KAAK,aAAa,QAAQ;AAAA,QAAA,CAC/C;AAAA,MAAA,CACF,IACD,EAAE,aAAa,CAAC,EAAE,KAAK,EAAE,GAAG,GAAG;AAAA,QACjC,QAAQ,OAAO,CAAC,KAAK;AAAA,QACrB,OAAO,MAAM,CAAC,KAAK;AAAA,QACnB,QAAQ,WAAW,IAAI;AAAA;AAAA;AAAA,QAGvB,WAAW,WAAW,QAAQ;AAAA,QAC9B,aAAa,WAAW,OAAO;AAAA,QAC/B,WAAW,MAAM,CAAC,KAAK;AAAA,MAAA,CACxB;AACC,eAAS,QAA0B,QAAQ;AAC7C,YAAM,OAAO,QAAQ,CAAC;AACtB,UAAI,KAAM,UAAS,UAAU,IAAI;AACjC,UAAI,aAAc,UAAS,GAAG,SAAS,MAAM,aAAa,CAAC,CAAC;AAC5D,aAAO,SAAS,QAAQ;AAExB,YAAM,QAAQ,SAAS,CAAC;AACxB,UAAI,OAAO;AAGT,eAAO;AAAA,UACL,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,GAAG,GAAG;AAAA,YACvB,aAAa;AAAA,YACb,MAAM,EAAE,QAAQ;AAAA,cACd,WAAW;AAAA,cACX,UAAU,CAAC,GAAG,CAAC;AAAA,cACf,MACE,2KACwE,KAAK;AAAA,YAAA,CAChF;AAAA,UAAA,CACF;AAAA,QAAA;AAAA,MAEL;AAAA,IACF;AAEA,WAAO,MAAM,IAAI;AACjB,cAAU,UAAU;AAEpB,QAAI,OAAO,QAAQ;AACjB,YAAM,UAAU,EAAE,aAAa,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;AAChE,iBAAW,UAAU,QAAQ,QAAA,IAAY,UAAU;AAUnD,UAAI,WAAW,WAAW,CAAC,UAAU,mBAAmB,YAAY,aAAa;AAC/E,2BAAmB,UAAU;AAC7B,kBAAU,UAAU;AACpB,kBAAU,MAAM,WAAW,OAAO;AAAA,MACpC;AAAA,IACF;AAAA,EACF,GAAG,CAAC,QAAQ,aAAa,SAAS,QAAQ,OAAO,QAAQ,QAAQ,CAAC;AAGlE,QAAM,WAAW,SAAS,WAAW,CAAC,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,KAAK,GAAG;AAE3E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAoBE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW,oCAAoC,WAAW,eAAe,EAAE,IAAI,aAAa,EAAE;AAAA,QAC9F,OAAO,EAAE,QAAQ,OAAA;AAAA,QAIhB,UAAA;AAAA,UAAA,YACC,oBAAC,WAAO,UAAA,wFAAuF;AAAA,UAkBjG,oBAAC,OAAA,EAAI,KAAK,QAAQ,OAAO,EAAE,QAAQ,QAAQ,OAAO,OAAA,GAAU,cAAW,OAAA,CAAO;AAAA,UAE7E,UAAU,CAAC,OAAO,8BAChB,OAAA,EAAI,WAAU,gGACZ,UAAA,IAAI,UAAA,CACP;AAAA,UAGD,CAAC,CAAC,QAAQ,UACT,oBAAC,OAAA,EAAI,WAAU,uIACZ,UAAA,OAAO,IAAI,CAAC,MACX;AAAA,YAAC;AAAA,YAAA;AAAA,cAEC,SAAS,MAAM,SAAS,EAAE,EAAE;AAAA,cAC5B,WAAW,iBACT,UAAU,EAAE,KAAK,6DAA6D,+EAChF;AAAA,cAEC,UAAA,EAAE;AAAA,YAAA;AAAA,YANE,EAAE;AAAA,UAAA,CAQV,GACH;AAAA,UAID,WACC,oBAAC,OAAA,EAAI,WAAU,gKACZ,UAAA,SACH;AAAA,UAaD,CAAC,CAAC,cAAc,IAAI,YAAY,mBAAmB,cAClD,qBAAC,OAAA,EAAI,WAAU,oKACb,UAAA;AAAA,YAAA,oBAAC,QAAA,EAAM,cAAI,SAAS,QAAQ,OAAO,OAAO,UAAU,CAAC,EAAA,CAAE;AAAA,YACvD;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,SAAS,MAAM,kBAAkB,UAAU;AAAA,gBAC3C,cAAY,IAAI,eAAe;AAAA,gBAC/B,OAAO,IAAI,eAAe;AAAA,gBAC1B,WAAU;AAAA,gBACX,UAAA;AAAA,cAAA;AAAA,YAAA;AAAA,UAED,EAAA,CACF;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA;AAIR;"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useMemo } from "react";
|
|
3
|
+
import Checkbox from "../../../../v1/primitives/forms/Checkbox/Checkbox.js";
|
|
3
4
|
const LABELS = {
|
|
4
5
|
titulo: "Filtros",
|
|
5
6
|
limpar: "Limpar",
|
|
@@ -168,16 +169,27 @@ function Campo({
|
|
|
168
169
|
{
|
|
169
170
|
className: `flex items-center gap-2 text-xs cursor-pointer px-1 py-0.5 rounded hover:bg-slate-50 dark:hover:bg-slate-800 ${o.total === 0 ? "opacity-45" : ""}`,
|
|
170
171
|
children: [
|
|
171
|
-
/* @__PURE__ */ jsx(
|
|
172
|
+
campo.unico ? /* @__PURE__ */ jsx(
|
|
172
173
|
"input",
|
|
173
174
|
{
|
|
174
|
-
type:
|
|
175
|
+
type: "radio",
|
|
175
176
|
checked: activo,
|
|
176
177
|
onClick: () => alternar(o.valor),
|
|
177
178
|
onChange: () => {
|
|
178
179
|
},
|
|
179
|
-
className: "accent-
|
|
180
|
+
className: "accent-primary"
|
|
180
181
|
}
|
|
182
|
+
) : (
|
|
183
|
+
// O V1 ainda não tem rádio: só as caixas passam para o Checkbox.
|
|
184
|
+
/* @__PURE__ */ jsx(
|
|
185
|
+
Checkbox,
|
|
186
|
+
{
|
|
187
|
+
checked: activo,
|
|
188
|
+
onClick: () => alternar(o.valor),
|
|
189
|
+
onChange: () => {
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
)
|
|
181
193
|
),
|
|
182
194
|
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate", title: o.rotulo, children: o.rotulo }),
|
|
183
195
|
o.total != null && /* @__PURE__ */ jsx("span", { className: "tabular-nums text-slate-400", children: o.total.toLocaleString("pt-PT") })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PainelFiltros.js","sources":["../../../../../../src/legacy/components/organisms/PainelFiltros/PainelFiltros.tsx"],"sourcesContent":["import React, { useMemo, useState } from 'react'\r\n\r\n/**\r\n * Painel lateral de filtros facetados.\r\n *\r\n * O que o torna útil é a CONTAGEM ao lado de cada opção: diz quantos registos\r\n * ficariam se a opção fosse escolhida, com os restantes filtros mantidos. Quem\r\n * monta o painel é que calcula essas contagens — o componente não sabe o que\r\n * está a contar, e tem de ser assim: a conta certa exige tirar a própria\r\n * dimensão do filtro, e isso só o servidor sabe fazer.\r\n *\r\n * Genérico de propósito: recebe secções e opções, devolve escolhas.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\n\r\nexport interface OpcaoFiltro {\r\n valor: string\r\n rotulo: string\r\n /** Quantos registos esta opção daria. `undefined` não desenha contagem. */\r\n total?: number\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport type TipoCampoFiltro = 'opcoes' | 'atalhos' | 'livre'\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface CampoFiltro {\r\n /** Identifica o campo nos valores devolvidos. */\r\n chave: string\r\n rotulo: string\r\n tipo?: TipoCampoFiltro\r\n opcoes?: OpcaoFiltro[]\r\n /**\r\n * Escolha única. Por omissão o campo é múltiplo — é o que a maioria das\r\n * dimensões quer, e forçar única obrigaria a limpar antes de trocar.\r\n */\r\n unico?: boolean\r\n /** A partir de quantas opções aparece a caixa de pesquisa. */\r\n pesquisarACimaDe?: number\r\n /** Conteúdo próprio (datas, interruptores…), quando `tipo` é `livre`. */\r\n conteudo?: React.ReactNode\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface SeccaoFiltros {\r\n chave: string\r\n titulo: string\r\n campos: CampoFiltro[]\r\n /** Aberta na primeira vez. Depois manda o que o utilizador fizer. */\r\n aberta?: boolean\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface PainelFiltrosLabels {\r\n titulo: string\r\n limpar: string\r\n pesquisar: string\r\n semOpcoes: string\r\n ocultar: string\r\n mostrar: string\r\n verTodas: string\r\n verMenos: string\r\n}\r\n\r\nconst LABELS: PainelFiltrosLabels = {\r\n titulo: 'Filtros',\r\n limpar: 'Limpar',\r\n pesquisar: 'Filtrar opções…',\r\n semOpcoes: 'Sem opções',\r\n ocultar: 'Ocultar filtros',\r\n mostrar: 'Mostrar filtros',\r\n verTodas: 'Ver todas ({n})',\r\n verMenos: 'Ver menos',\r\n}\r\n\r\n/** Quantas opções se mostram antes do \"ver todas\". */\r\nconst VISIVEIS = 8\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface PainelFiltrosProps {\r\n seccoes: SeccaoFiltros[]\r\n /** Valores actuais: chave do campo → valores escolhidos. */\r\n valores: Record<string, string[]>\r\n onChange: (chave: string, valores: string[]) => void\r\n onLimpar?: () => void\r\n labels?: Partial<PainelFiltrosLabels>\r\n className?: string\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport default function PainelFiltros({\r\n seccoes,\r\n valores,\r\n onChange,\r\n onLimpar,\r\n labels,\r\n className,\r\n}: PainelFiltrosProps) {\r\n const txt = { ...LABELS, ...labels }\r\n const [fechado, setFechado] = useState(false)\r\n const [abertas, setAbertas] = useState<Record<string, boolean>>(() =>\r\n Object.fromEntries(seccoes.map((s) => [s.chave, s.aberta ?? true]))\r\n )\r\n\r\n const activos = useMemo(\r\n () => Object.values(valores).reduce((n, v) => n + (v?.length ?? 0), 0),\r\n [valores]\r\n )\r\n\r\n if (fechado) {\r\n return (\r\n <button\r\n onClick={() => setFechado(false)}\r\n title={txt.mostrar}\r\n className=\"h-full px-2 border-r border-slate-200 bg-white text-slate-500 hover:text-proef-navy dark:bg-slate-900 dark:border-slate-700 dark:text-slate-400 dark:hover:text-white\"\r\n >\r\n <span className=\"[writing-mode:vertical-rl] text-xs font-semibold tracking-wide\">\r\n {txt.titulo}\r\n {activos ? ` · ${activos}` : ''}\r\n </span>\r\n </button>\r\n )\r\n }\r\n\r\n return (\r\n <div className={`bg-white border-r border-slate-200 dark:bg-slate-900 dark:border-slate-700 flex flex-col ${className ?? ''}`}>\r\n <div className=\"flex items-center gap-2 px-3 py-2 border-b border-slate-200 dark:border-slate-700\">\r\n <h2 className=\"text-sm font-black uppercase tracking-tight text-proef-navy dark:text-slate-100\">{txt.titulo}</h2>\r\n {!!activos && (\r\n <span className=\"text-[10px] font-bold bg-proef-navy text-white rounded-full px-1.5 py-0.5\">\r\n {activos}\r\n </span>\r\n )}\r\n <div className=\"ml-auto flex items-center gap-1\">\r\n <button\r\n onClick={onLimpar}\r\n disabled={!activos}\r\n className=\"text-xs text-slate-500 hover:text-proef-navy dark:text-slate-400 dark:hover:text-white disabled:opacity-40\"\r\n >\r\n {txt.limpar}\r\n </button>\r\n <button\r\n onClick={() => setFechado(true)}\r\n title={txt.ocultar}\r\n className=\"text-slate-400 hover:text-proef-navy dark:hover:text-white px-1\"\r\n >\r\n ‹\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div className=\"overflow-auto flex-1 divide-y divide-slate-100\">\r\n {seccoes.map((seccao) => {\r\n const nesta = seccao.campos.reduce((n, c) => n + (valores[c.chave]?.length ?? 0), 0)\r\n const aberta = abertas[seccao.chave] ?? true\r\n return (\r\n <div key={seccao.chave}>\r\n <button\r\n onClick={() => setAbertas((a) => ({ ...a, [seccao.chave]: !aberta }))}\r\n className=\"w-full flex items-center gap-2 px-3 py-2 text-left text-xs font-bold uppercase tracking-wide text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-800\"\r\n >\r\n <span className=\"text-slate-400 w-3\">{aberta ? '−' : '+'}</span>\r\n {seccao.titulo}\r\n {!!nesta && (\r\n <span className=\"text-[10px] font-bold bg-proef-navy text-white rounded-full px-1.5\">\r\n {nesta}\r\n </span>\r\n )}\r\n </button>\r\n {aberta && (\r\n <div className=\"px-3 pb-3 space-y-3\">\r\n {seccao.campos.map((campo) => (\r\n <Campo\r\n key={campo.chave}\r\n campo={campo}\r\n escolhidos={valores[campo.chave] ?? []}\r\n onChange={(v) => onChange(campo.chave, v)}\r\n txt={txt}\r\n />\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )\r\n })}\r\n </div>\r\n </div>\r\n )\r\n}\r\n\r\nfunction Campo({\r\n campo,\r\n escolhidos,\r\n onChange,\r\n txt,\r\n}: {\r\n campo: CampoFiltro\r\n escolhidos: string[]\r\n onChange: (valores: string[]) => void\r\n txt: PainelFiltrosLabels\r\n}) {\r\n const [pesquisa, setPesquisa] = useState('')\r\n const [todas, setTodas] = useState(false)\r\n\r\n if (campo.tipo === 'livre') {\r\n return (\r\n <div>\r\n <div className=\"text-[11px] font-semibold text-slate-500 dark:text-slate-400 mb-1\">{campo.rotulo}</div>\r\n {campo.conteudo}\r\n </div>\r\n )\r\n }\r\n\r\n const opcoes = campo.opcoes ?? []\r\n const limiar = campo.pesquisarACimaDe ?? 12\r\n const filtradas = pesquisa\r\n ? opcoes.filter((o) => semAcentos(o.rotulo).includes(semAcentos(pesquisa)))\r\n : opcoes\r\n // As escolhidas ficam sempre visíveis, mesmo quando a lista está cortada:\r\n // esconder um filtro activo é a maneira mais rápida de alguém não perceber\r\n // porque é que a lista está pequena.\r\n const visiveis = todas\r\n ? filtradas\r\n : [\r\n ...filtradas.slice(0, VISIVEIS),\r\n ...filtradas.slice(VISIVEIS).filter((o) => escolhidos.includes(o.valor)),\r\n ]\r\n\r\n const alternar = (valor: string): void => {\r\n if (campo.unico) {\r\n onChange(escolhidos[0] === valor ? [] : [valor])\r\n return\r\n }\r\n onChange(\r\n escolhidos.includes(valor) ? escolhidos.filter((v) => v !== valor) : [...escolhidos, valor]\r\n )\r\n }\r\n\r\n if (campo.tipo === 'atalhos') {\r\n return (\r\n <div>\r\n <div className=\"text-[11px] font-semibold text-slate-500 dark:text-slate-400 mb-1\">{campo.rotulo}</div>\r\n <div className=\"grid grid-cols-2 gap-1\">\r\n {opcoes.map((o) => {\r\n const activo = escolhidos.includes(o.valor)\r\n return (\r\n <button\r\n key={o.valor}\r\n onClick={() => alternar(o.valor)}\r\n className={`flex items-center justify-between gap-1 px-2 py-1 rounded border text-[11px] ${\r\n activo\r\n ? 'border-proef-navy bg-sky-50 text-proef-navy font-semibold dark:border-sky-400 dark:bg-sky-500/15 dark:text-sky-300'\r\n : 'border-slate-200 text-slate-600 hover:bg-slate-50 dark:border-slate-700 dark:text-slate-300 dark:hover:bg-slate-800'\r\n }`}\r\n >\r\n <span className=\"truncate\">{o.rotulo}</span>\r\n {o.total != null && <b className=\"tabular-nums\">{o.total.toLocaleString('pt-PT')}</b>}\r\n </button>\r\n )\r\n })}\r\n </div>\r\n </div>\r\n )\r\n }\r\n\r\n return (\r\n <div>\r\n <div className=\"text-[11px] font-semibold text-slate-500 dark:text-slate-400 mb-1\">{campo.rotulo}</div>\r\n {opcoes.length > limiar && (\r\n <input\r\n type=\"search\"\r\n value={pesquisa}\r\n onChange={(e) => setPesquisa(e.target.value)}\r\n placeholder={txt.pesquisar}\r\n className=\"w-full mb-1 border border-slate-300 rounded px-2 py-1 text-xs bg-white dark:bg-slate-800 dark:border-slate-600 dark:text-slate-100\"\r\n />\r\n )}\r\n <div className=\"space-y-0.5\">\r\n {!visiveis.length && <div className=\"text-xs text-slate-400\">{txt.semOpcoes}</div>}\r\n {visiveis.map((o) => {\r\n const activo = escolhidos.includes(o.valor)\r\n return (\r\n <label\r\n key={o.valor}\r\n className={`flex items-center gap-2 text-xs cursor-pointer px-1 py-0.5 rounded hover:bg-slate-50 dark:hover:bg-slate-800 ${\r\n o.total === 0 ? 'opacity-45' : ''\r\n }`}\r\n >\r\n {/*\r\n `onClick` e não `onChange`.\r\n\r\n Um radio já marcado não dispara `onChange` ao ser clicado outra\r\n vez — o estado não muda, logo não há evento — e a lógica de\r\n desmarcar que o `alternar` tem nunca chegava a correr. Só se\r\n notava nos campos de escolha única: escolhia-se uma prioridade\r\n e a única saída era limpar o painel todo.\r\n\r\n `onClick` dispara sempre. O `onChange` vazio existe porque o\r\n React avisa quando um input é `checked` sem ele.\r\n */}\r\n <input\r\n type={campo.unico ? 'radio' : 'checkbox'}\r\n checked={activo}\r\n onClick={() => alternar(o.valor)}\r\n onChange={() => { /* o clique é tratado em onClick */ }}\r\n className=\"accent-proef-navy\"\r\n />\r\n <span className=\"flex-1 truncate\" title={o.rotulo}>{o.rotulo}</span>\r\n {o.total != null && (\r\n <span className=\"tabular-nums text-slate-400\">{o.total.toLocaleString('pt-PT')}</span>\r\n )}\r\n </label>\r\n )\r\n })}\r\n </div>\r\n {filtradas.length > VISIVEIS && (\r\n <button\r\n onClick={() => setTodas((v) => !v)}\r\n className=\"mt-1 text-[11px] text-proef-navy dark:text-sky-300 hover:underline\"\r\n >\r\n {todas ? txt.verMenos : txt.verTodas.replace('{n}', String(filtradas.length))}\r\n </button>\r\n )}\r\n </div>\r\n )\r\n}\r\n\r\n/** Comparação sem acentos nem maiúsculas: \"Guimarães\" tem de encontrar-se por \"guimaraes\". */\r\nfunction semAcentos(texto: string): string {\r\n return texto.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase()\r\n}\r\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PainelFiltros.js","sources":["../../../../../../src/legacy/components/organisms/PainelFiltros/PainelFiltros.tsx"],"sourcesContent":["import React, { useMemo, useState } from 'react'\r\nimport Checkbox from '../../../../v1/primitives/forms/Checkbox/Checkbox'\r\n\r\n/**\r\n * Painel lateral de filtros facetados.\r\n *\r\n * O que o torna útil é a CONTAGEM ao lado de cada opção: diz quantos registos\r\n * ficariam se a opção fosse escolhida, com os restantes filtros mantidos. Quem\r\n * monta o painel é que calcula essas contagens — o componente não sabe o que\r\n * está a contar, e tem de ser assim: a conta certa exige tirar a própria\r\n * dimensão do filtro, e isso só o servidor sabe fazer.\r\n *\r\n * Genérico de propósito: recebe secções e opções, devolve escolhas.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\n\r\nexport interface OpcaoFiltro {\r\n valor: string\r\n rotulo: string\r\n /** Quantos registos esta opção daria. `undefined` não desenha contagem. */\r\n total?: number\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport type TipoCampoFiltro = 'opcoes' | 'atalhos' | 'livre'\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface CampoFiltro {\r\n /** Identifica o campo nos valores devolvidos. */\r\n chave: string\r\n rotulo: string\r\n tipo?: TipoCampoFiltro\r\n opcoes?: OpcaoFiltro[]\r\n /**\r\n * Escolha única. Por omissão o campo é múltiplo — é o que a maioria das\r\n * dimensões quer, e forçar única obrigaria a limpar antes de trocar.\r\n */\r\n unico?: boolean\r\n /** A partir de quantas opções aparece a caixa de pesquisa. */\r\n pesquisarACimaDe?: number\r\n /** Conteúdo próprio (datas, interruptores…), quando `tipo` é `livre`. */\r\n conteudo?: React.ReactNode\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface SeccaoFiltros {\r\n chave: string\r\n titulo: string\r\n campos: CampoFiltro[]\r\n /** Aberta na primeira vez. Depois manda o que o utilizador fizer. */\r\n aberta?: boolean\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface PainelFiltrosLabels {\r\n titulo: string\r\n limpar: string\r\n pesquisar: string\r\n semOpcoes: string\r\n ocultar: string\r\n mostrar: string\r\n verTodas: string\r\n verMenos: string\r\n}\r\n\r\nconst LABELS: PainelFiltrosLabels = {\r\n titulo: 'Filtros',\r\n limpar: 'Limpar',\r\n pesquisar: 'Filtrar opções…',\r\n semOpcoes: 'Sem opções',\r\n ocultar: 'Ocultar filtros',\r\n mostrar: 'Mostrar filtros',\r\n verTodas: 'Ver todas ({n})',\r\n verMenos: 'Ver menos',\r\n}\r\n\r\n/** Quantas opções se mostram antes do \"ver todas\". */\r\nconst VISIVEIS = 8\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface PainelFiltrosProps {\r\n seccoes: SeccaoFiltros[]\r\n /** Valores actuais: chave do campo → valores escolhidos. */\r\n valores: Record<string, string[]>\r\n onChange: (chave: string, valores: string[]) => void\r\n onLimpar?: () => void\r\n labels?: Partial<PainelFiltrosLabels>\r\n className?: string\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport default function PainelFiltros({\r\n seccoes,\r\n valores,\r\n onChange,\r\n onLimpar,\r\n labels,\r\n className,\r\n}: PainelFiltrosProps) {\r\n const txt = { ...LABELS, ...labels }\r\n const [fechado, setFechado] = useState(false)\r\n const [abertas, setAbertas] = useState<Record<string, boolean>>(() =>\r\n Object.fromEntries(seccoes.map((s) => [s.chave, s.aberta ?? true]))\r\n )\r\n\r\n const activos = useMemo(\r\n () => Object.values(valores).reduce((n, v) => n + (v?.length ?? 0), 0),\r\n [valores]\r\n )\r\n\r\n if (fechado) {\r\n return (\r\n <button\r\n onClick={() => setFechado(false)}\r\n title={txt.mostrar}\r\n className=\"h-full px-2 border-r border-slate-200 bg-white text-slate-500 hover:text-proef-navy dark:bg-slate-900 dark:border-slate-700 dark:text-slate-400 dark:hover:text-white\"\r\n >\r\n <span className=\"[writing-mode:vertical-rl] text-xs font-semibold tracking-wide\">\r\n {txt.titulo}\r\n {activos ? ` · ${activos}` : ''}\r\n </span>\r\n </button>\r\n )\r\n }\r\n\r\n return (\r\n <div className={`bg-white border-r border-slate-200 dark:bg-slate-900 dark:border-slate-700 flex flex-col ${className ?? ''}`}>\r\n <div className=\"flex items-center gap-2 px-3 py-2 border-b border-slate-200 dark:border-slate-700\">\r\n <h2 className=\"text-sm font-black uppercase tracking-tight text-proef-navy dark:text-slate-100\">{txt.titulo}</h2>\r\n {!!activos && (\r\n <span className=\"text-[10px] font-bold bg-proef-navy text-white rounded-full px-1.5 py-0.5\">\r\n {activos}\r\n </span>\r\n )}\r\n <div className=\"ml-auto flex items-center gap-1\">\r\n <button\r\n onClick={onLimpar}\r\n disabled={!activos}\r\n className=\"text-xs text-slate-500 hover:text-proef-navy dark:text-slate-400 dark:hover:text-white disabled:opacity-40\"\r\n >\r\n {txt.limpar}\r\n </button>\r\n <button\r\n onClick={() => setFechado(true)}\r\n title={txt.ocultar}\r\n className=\"text-slate-400 hover:text-proef-navy dark:hover:text-white px-1\"\r\n >\r\n ‹\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div className=\"overflow-auto flex-1 divide-y divide-slate-100\">\r\n {seccoes.map((seccao) => {\r\n const nesta = seccao.campos.reduce((n, c) => n + (valores[c.chave]?.length ?? 0), 0)\r\n const aberta = abertas[seccao.chave] ?? true\r\n return (\r\n <div key={seccao.chave}>\r\n <button\r\n onClick={() => setAbertas((a) => ({ ...a, [seccao.chave]: !aberta }))}\r\n className=\"w-full flex items-center gap-2 px-3 py-2 text-left text-xs font-bold uppercase tracking-wide text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-800\"\r\n >\r\n <span className=\"text-slate-400 w-3\">{aberta ? '−' : '+'}</span>\r\n {seccao.titulo}\r\n {!!nesta && (\r\n <span className=\"text-[10px] font-bold bg-proef-navy text-white rounded-full px-1.5\">\r\n {nesta}\r\n </span>\r\n )}\r\n </button>\r\n {aberta && (\r\n <div className=\"px-3 pb-3 space-y-3\">\r\n {seccao.campos.map((campo) => (\r\n <Campo\r\n key={campo.chave}\r\n campo={campo}\r\n escolhidos={valores[campo.chave] ?? []}\r\n onChange={(v) => onChange(campo.chave, v)}\r\n txt={txt}\r\n />\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )\r\n })}\r\n </div>\r\n </div>\r\n )\r\n}\r\n\r\nfunction Campo({\r\n campo,\r\n escolhidos,\r\n onChange,\r\n txt,\r\n}: {\r\n campo: CampoFiltro\r\n escolhidos: string[]\r\n onChange: (valores: string[]) => void\r\n txt: PainelFiltrosLabels\r\n}) {\r\n const [pesquisa, setPesquisa] = useState('')\r\n const [todas, setTodas] = useState(false)\r\n\r\n if (campo.tipo === 'livre') {\r\n return (\r\n <div>\r\n <div className=\"text-[11px] font-semibold text-slate-500 dark:text-slate-400 mb-1\">{campo.rotulo}</div>\r\n {campo.conteudo}\r\n </div>\r\n )\r\n }\r\n\r\n const opcoes = campo.opcoes ?? []\r\n const limiar = campo.pesquisarACimaDe ?? 12\r\n const filtradas = pesquisa\r\n ? opcoes.filter((o) => semAcentos(o.rotulo).includes(semAcentos(pesquisa)))\r\n : opcoes\r\n // As escolhidas ficam sempre visíveis, mesmo quando a lista está cortada:\r\n // esconder um filtro activo é a maneira mais rápida de alguém não perceber\r\n // porque é que a lista está pequena.\r\n const visiveis = todas\r\n ? filtradas\r\n : [\r\n ...filtradas.slice(0, VISIVEIS),\r\n ...filtradas.slice(VISIVEIS).filter((o) => escolhidos.includes(o.valor)),\r\n ]\r\n\r\n const alternar = (valor: string): void => {\r\n if (campo.unico) {\r\n onChange(escolhidos[0] === valor ? [] : [valor])\r\n return\r\n }\r\n onChange(\r\n escolhidos.includes(valor) ? escolhidos.filter((v) => v !== valor) : [...escolhidos, valor]\r\n )\r\n }\r\n\r\n if (campo.tipo === 'atalhos') {\r\n return (\r\n <div>\r\n <div className=\"text-[11px] font-semibold text-slate-500 dark:text-slate-400 mb-1\">{campo.rotulo}</div>\r\n <div className=\"grid grid-cols-2 gap-1\">\r\n {opcoes.map((o) => {\r\n const activo = escolhidos.includes(o.valor)\r\n return (\r\n <button\r\n key={o.valor}\r\n onClick={() => alternar(o.valor)}\r\n className={`flex items-center justify-between gap-1 px-2 py-1 rounded border text-[11px] ${\r\n activo\r\n ? 'border-proef-navy bg-sky-50 text-proef-navy font-semibold dark:border-sky-400 dark:bg-sky-500/15 dark:text-sky-300'\r\n : 'border-slate-200 text-slate-600 hover:bg-slate-50 dark:border-slate-700 dark:text-slate-300 dark:hover:bg-slate-800'\r\n }`}\r\n >\r\n <span className=\"truncate\">{o.rotulo}</span>\r\n {o.total != null && <b className=\"tabular-nums\">{o.total.toLocaleString('pt-PT')}</b>}\r\n </button>\r\n )\r\n })}\r\n </div>\r\n </div>\r\n )\r\n }\r\n\r\n return (\r\n <div>\r\n <div className=\"text-[11px] font-semibold text-slate-500 dark:text-slate-400 mb-1\">{campo.rotulo}</div>\r\n {opcoes.length > limiar && (\r\n <input\r\n type=\"search\"\r\n value={pesquisa}\r\n onChange={(e) => setPesquisa(e.target.value)}\r\n placeholder={txt.pesquisar}\r\n className=\"w-full mb-1 border border-slate-300 rounded px-2 py-1 text-xs bg-white dark:bg-slate-800 dark:border-slate-600 dark:text-slate-100\"\r\n />\r\n )}\r\n <div className=\"space-y-0.5\">\r\n {!visiveis.length && <div className=\"text-xs text-slate-400\">{txt.semOpcoes}</div>}\r\n {visiveis.map((o) => {\r\n const activo = escolhidos.includes(o.valor)\r\n return (\r\n <label\r\n key={o.valor}\r\n className={`flex items-center gap-2 text-xs cursor-pointer px-1 py-0.5 rounded hover:bg-slate-50 dark:hover:bg-slate-800 ${\r\n o.total === 0 ? 'opacity-45' : ''\r\n }`}\r\n >\r\n {/*\r\n `onClick` e não `onChange`.\r\n\r\n Um radio já marcado não dispara `onChange` ao ser clicado outra\r\n vez — o estado não muda, logo não há evento — e a lógica de\r\n desmarcar que o `alternar` tem nunca chegava a correr. Só se\r\n notava nos campos de escolha única: escolhia-se uma prioridade\r\n e a única saída era limpar o painel todo.\r\n\r\n `onClick` dispara sempre. O `onChange` vazio existe porque o\r\n React avisa quando um input é `checked` sem ele.\r\n */}\r\n {campo.unico ? (\r\n <input\r\n type=\"radio\"\r\n checked={activo}\r\n onClick={() => alternar(o.valor)}\r\n onChange={() => { /* o clique é tratado em onClick */ }}\r\n className=\"accent-primary\"\r\n />\r\n ) : (\r\n // O V1 ainda não tem rádio: só as caixas passam para o Checkbox.\r\n <Checkbox\r\n checked={activo}\r\n onClick={() => alternar(o.valor)}\r\n onChange={() => { /* o clique é tratado em onClick */ }}\r\n />\r\n )}\r\n <span className=\"flex-1 truncate\" title={o.rotulo}>{o.rotulo}</span>\r\n {o.total != null && (\r\n <span className=\"tabular-nums text-slate-400\">{o.total.toLocaleString('pt-PT')}</span>\r\n )}\r\n </label>\r\n )\r\n })}\r\n </div>\r\n {filtradas.length > VISIVEIS && (\r\n <button\r\n onClick={() => setTodas((v) => !v)}\r\n className=\"mt-1 text-[11px] text-proef-navy dark:text-sky-300 hover:underline\"\r\n >\r\n {todas ? txt.verMenos : txt.verTodas.replace('{n}', String(filtradas.length))}\r\n </button>\r\n )}\r\n </div>\r\n )\r\n}\r\n\r\n/** Comparação sem acentos nem maiúsculas: \"Guimarães\" tem de encontrar-se por \"guimaraes\". */\r\nfunction semAcentos(texto: string): string {\r\n return texto.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase()\r\n}\r\n"],"names":[],"mappings":"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"}
|
|
@@ -3,11 +3,11 @@ import { useId, useState, useEffect } from "react";
|
|
|
3
3
|
import { faSave, faMinus, faPlus, faQuestion } from "@fortawesome/free-solid-svg-icons";
|
|
4
4
|
import { ActionButtonThemeStyled, IconTextDarkThemeStyled, IconButtonThemeStyled, IconTextLightThemeStyled } from "../../../styles/themes/ButtonThemes.js";
|
|
5
5
|
import Modal from "../../molecules/Modal/Modal.js";
|
|
6
|
-
import
|
|
6
|
+
import SearchInput from "../../../../v1/primitives/forms/SearchInput/SearchInput.js";
|
|
7
7
|
import { LazyLoader } from "../LazyLoader/LazyLoader.js";
|
|
8
8
|
import './Selector.css';/* empty css */
|
|
9
9
|
import Spinner from "../../layout/Spinner/Spinner.js";
|
|
10
|
-
import
|
|
10
|
+
import Tooltip from "../../../../v1/components/overlays/Tooltip/Tooltip.js";
|
|
11
11
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
12
12
|
import ActionButton from "../../atoms/Button/variants/ActionButton.js";
|
|
13
13
|
import IconText from "../../atoms/IconText/IconText.js";
|
|
@@ -87,7 +87,7 @@ const Selector = ({
|
|
|
87
87
|
theme: IconButtonThemeStyled
|
|
88
88
|
}
|
|
89
89
|
) : /* @__PURE__ */ jsx(
|
|
90
|
-
|
|
90
|
+
Tooltip,
|
|
91
91
|
{
|
|
92
92
|
interactive: true,
|
|
93
93
|
tip: onDisabled ? onDisabled(element) : isBlocked ? /* @__PURE__ */ jsx("div", { children: "Bloqueado..." }) : /* @__PURE__ */ jsx("div", { children: "Sem dados...." }),
|
|
@@ -142,7 +142,7 @@ const Selector = ({
|
|
|
142
142
|
onConfirm(selectedElements);
|
|
143
143
|
};
|
|
144
144
|
return /* @__PURE__ */ jsx(Modal, { title, onClose, children: /* @__PURE__ */ jsxs("div", { className: "cui-selector", children: [
|
|
145
|
-
/* @__PURE__ */ jsx("div", { className: "cui-selector__search", children: /* @__PURE__ */ jsx(
|
|
145
|
+
/* @__PURE__ */ jsx("div", { className: "cui-selector__search", children: /* @__PURE__ */ jsx(SearchInput, { value: filter.term.text, onChange: handleSearch, className: "w-full" }) }),
|
|
146
146
|
/* @__PURE__ */ jsx("div", { className: "cui-selector__selected", children: renderSelectedElements() }),
|
|
147
147
|
/* @__PURE__ */ jsx("div", { id: scrollContainerId, className: "cui-selector__elements", children: /* @__PURE__ */ jsx(
|
|
148
148
|
LazyLoader,
|