@dataemergencia/emergency 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/iife/emergency.iife.global.js +2 -2
- package/dist/index.cjs +639 -130
- package/dist/index.js +639 -130
- package/dist/styles.css +300 -23
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1576,6 +1576,12 @@ var panel_module_default = {
|
|
|
1576
1576
|
"de-emergency-slide-in": "de-emergency-de-emergency-slide-in-nA5Hwa",
|
|
1577
1577
|
"de-emergency-slide-up": "de-emergency-de-emergency-slide-up-nA5Hwa",
|
|
1578
1578
|
"de-emergency-spin": "de-emergency-de-emergency-spin-nA5Hwa",
|
|
1579
|
+
"emergency-alert-row__badge": "de-emergency-emergency-alert-row__badge-nA5Hwa",
|
|
1580
|
+
"emergency-alert-row__button": "de-emergency-emergency-alert-row__button-nA5Hwa",
|
|
1581
|
+
"emergency-alert-row__stat": "de-emergency-emergency-alert-row__stat-nA5Hwa",
|
|
1582
|
+
"emergency-alert-row__stats": "de-emergency-emergency-alert-row__stats-nA5Hwa",
|
|
1583
|
+
"emergency-alert-row__time": "de-emergency-emergency-alert-row__time-nA5Hwa",
|
|
1584
|
+
"emergency-alert-row__top": "de-emergency-emergency-alert-row__top-nA5Hwa",
|
|
1579
1585
|
"emergency-availability": "de-emergency-emergency-availability-nA5Hwa",
|
|
1580
1586
|
"emergency-availability__card": "de-emergency-emergency-availability__card-nA5Hwa",
|
|
1581
1587
|
"emergency-availability__card-body": "de-emergency-emergency-availability__card-body-nA5Hwa",
|
|
@@ -1597,8 +1603,22 @@ var panel_module_default = {
|
|
|
1597
1603
|
"emergency-confirmation__icon": "de-emergency-emergency-confirmation__icon-nA5Hwa",
|
|
1598
1604
|
"emergency-confirmation__icon--danger": "de-emergency-emergency-confirmation__icon--danger-nA5Hwa",
|
|
1599
1605
|
"emergency-confirmation__icon--information": "de-emergency-emergency-confirmation__icon--information-nA5Hwa",
|
|
1606
|
+
"emergency-confirmation__spinner": "de-emergency-emergency-confirmation__spinner-nA5Hwa",
|
|
1600
1607
|
"emergency-confirmation__text": "de-emergency-emergency-confirmation__text-nA5Hwa",
|
|
1601
1608
|
"emergency-confirmation__title": "de-emergency-emergency-confirmation__title-nA5Hwa",
|
|
1609
|
+
"emergency-details": "de-emergency-emergency-details-nA5Hwa",
|
|
1610
|
+
"emergency-details__balance": "de-emergency-emergency-details__balance-nA5Hwa",
|
|
1611
|
+
"emergency-details__card": "de-emergency-emergency-details__card-nA5Hwa",
|
|
1612
|
+
"emergency-details__card-body": "de-emergency-emergency-details__card-body-nA5Hwa",
|
|
1613
|
+
"emergency-details__card-header": "de-emergency-emergency-details__card-header-nA5Hwa",
|
|
1614
|
+
"emergency-details__card-title": "de-emergency-emergency-details__card-title-nA5Hwa",
|
|
1615
|
+
"emergency-details__empty": "de-emergency-emergency-details__empty-nA5Hwa",
|
|
1616
|
+
"emergency-details__heading": "de-emergency-emergency-details__heading-nA5Hwa",
|
|
1617
|
+
"emergency-details__history-button": "de-emergency-emergency-details__history-button-nA5Hwa",
|
|
1618
|
+
"emergency-details__list": "de-emergency-emergency-details__list-nA5Hwa",
|
|
1619
|
+
"emergency-details__section": "de-emergency-emergency-details__section-nA5Hwa",
|
|
1620
|
+
"emergency-details__subtitle": "de-emergency-emergency-details__subtitle-nA5Hwa",
|
|
1621
|
+
"emergency-details__title": "de-emergency-emergency-details__title-nA5Hwa",
|
|
1602
1622
|
"emergency-error-message": "de-emergency-emergency-error-message-nA5Hwa",
|
|
1603
1623
|
"emergency-error-message__container": "de-emergency-emergency-error-message__container-nA5Hwa",
|
|
1604
1624
|
"emergency-error-message__content": "de-emergency-emergency-error-message__content-nA5Hwa",
|
|
@@ -1609,11 +1629,33 @@ var panel_module_default = {
|
|
|
1609
1629
|
"emergency-error-message__title": "de-emergency-emergency-error-message__title-nA5Hwa",
|
|
1610
1630
|
"emergency-feedback": "de-emergency-emergency-feedback-nA5Hwa",
|
|
1611
1631
|
"emergency-feedback__dismiss": "de-emergency-emergency-feedback__dismiss-nA5Hwa",
|
|
1632
|
+
"emergency-history": "de-emergency-emergency-history-nA5Hwa",
|
|
1633
|
+
"emergency-history__empty": "de-emergency-emergency-history__empty-nA5Hwa",
|
|
1634
|
+
"emergency-history__heading": "de-emergency-emergency-history__heading-nA5Hwa",
|
|
1635
|
+
"emergency-history__list": "de-emergency-emergency-history__list-nA5Hwa",
|
|
1636
|
+
"emergency-history__more": "de-emergency-emergency-history__more-nA5Hwa",
|
|
1637
|
+
"emergency-history__subtitle": "de-emergency-emergency-history__subtitle-nA5Hwa",
|
|
1638
|
+
"emergency-history__title": "de-emergency-emergency-history__title-nA5Hwa",
|
|
1639
|
+
"emergency-message-detail": "de-emergency-emergency-message-detail-nA5Hwa",
|
|
1640
|
+
"emergency-message-detail__back": "de-emergency-emergency-message-detail__back-nA5Hwa",
|
|
1641
|
+
"emergency-message-detail__card": "de-emergency-emergency-message-detail__card-nA5Hwa",
|
|
1642
|
+
"emergency-message-detail__card-header": "de-emergency-emergency-message-detail__card-header-nA5Hwa",
|
|
1643
|
+
"emergency-message-detail__card-title": "de-emergency-emergency-message-detail__card-title-nA5Hwa",
|
|
1644
|
+
"emergency-message-detail__empty": "de-emergency-emergency-message-detail__empty-nA5Hwa",
|
|
1645
|
+
"emergency-message-detail__empty-text": "de-emergency-emergency-message-detail__empty-text-nA5Hwa",
|
|
1646
|
+
"emergency-message-detail__label": "de-emergency-emergency-message-detail__label-nA5Hwa",
|
|
1647
|
+
"emergency-message-detail__list": "de-emergency-emergency-message-detail__list-nA5Hwa",
|
|
1648
|
+
"emergency-message-detail__row": "de-emergency-emergency-message-detail__row-nA5Hwa",
|
|
1649
|
+
"emergency-message-detail__value": "de-emergency-emergency-message-detail__value-nA5Hwa",
|
|
1612
1650
|
"emergency-panel": "de-emergency-emergency-panel-nA5Hwa",
|
|
1613
1651
|
"emergency-panel__availability-region": "de-emergency-emergency-panel__availability-region-nA5Hwa",
|
|
1614
1652
|
"emergency-panel__body": "de-emergency-emergency-panel__body-nA5Hwa",
|
|
1653
|
+
"emergency-panel__body--details": "de-emergency-emergency-panel__body--details-nA5Hwa",
|
|
1654
|
+
"emergency-panel__body--history": "de-emergency-emergency-panel__body--history-nA5Hwa",
|
|
1655
|
+
"emergency-panel__body--message-detail": "de-emergency-emergency-panel__body--message-detail-nA5Hwa",
|
|
1615
1656
|
"emergency-panel__body--pre-emergency": "de-emergency-emergency-panel__body--pre-emergency-nA5Hwa",
|
|
1616
1657
|
"emergency-panel__body--pricing": "de-emergency-emergency-panel__body--pricing-nA5Hwa",
|
|
1658
|
+
"emergency-panel__body--primary": "de-emergency-emergency-panel__body--primary-nA5Hwa",
|
|
1617
1659
|
"emergency-panel__body-split": "de-emergency-emergency-panel__body-split-nA5Hwa",
|
|
1618
1660
|
"emergency-panel__body-split--with-availability": "de-emergency-emergency-panel__body-split--with-availability-nA5Hwa",
|
|
1619
1661
|
"emergency-panel__body-title": "de-emergency-emergency-panel__body-title-nA5Hwa",
|
|
@@ -1622,13 +1664,14 @@ var panel_module_default = {
|
|
|
1622
1664
|
"emergency-panel__button--emergency": "de-emergency-emergency-panel__button--emergency-nA5Hwa",
|
|
1623
1665
|
"emergency-panel__button--simulation": "de-emergency-emergency-panel__button--simulation-nA5Hwa",
|
|
1624
1666
|
"emergency-panel__close-button": "de-emergency-emergency-panel__close-button-nA5Hwa",
|
|
1667
|
+
"emergency-panel__close-button--prompt": "de-emergency-emergency-panel__close-button--prompt-nA5Hwa",
|
|
1625
1668
|
"emergency-panel__close-icon": "de-emergency-emergency-panel__close-icon-nA5Hwa",
|
|
1626
|
-
"emergency-panel__divider": "de-emergency-emergency-panel__divider-nA5Hwa",
|
|
1627
1669
|
"emergency-panel__footer": "de-emergency-emergency-panel__footer-nA5Hwa",
|
|
1628
1670
|
"emergency-panel__footer--single": "de-emergency-emergency-panel__footer--single-nA5Hwa",
|
|
1671
|
+
"emergency-panel__footer--square": "de-emergency-emergency-panel__footer--square-nA5Hwa",
|
|
1629
1672
|
"emergency-panel__header": "de-emergency-emergency-panel__header-nA5Hwa",
|
|
1630
|
-
"emergency-panel__header--close-only": "de-emergency-emergency-panel__header--close-only-nA5Hwa",
|
|
1631
1673
|
"emergency-panel__prompt-region": "de-emergency-emergency-panel__prompt-region-nA5Hwa",
|
|
1674
|
+
"emergency-panel__prompt-region--idle": "de-emergency-emergency-panel__prompt-region--idle-nA5Hwa",
|
|
1632
1675
|
"emergency-panel__sr-only": "de-emergency-emergency-panel__sr-only-nA5Hwa",
|
|
1633
1676
|
"emergency-panel__title": "de-emergency-emergency-panel__title-nA5Hwa",
|
|
1634
1677
|
"emergency-panel--hidden": "de-emergency-emergency-panel--hidden-nA5Hwa",
|
|
@@ -1724,11 +1767,12 @@ function cls(...names) {
|
|
|
1724
1767
|
|
|
1725
1768
|
// src/react/components/EmergencyConfirmation/index.tsx
|
|
1726
1769
|
function EmergencyConfirmation(props) {
|
|
1727
|
-
const { abortConfirmation, simulationConfirmation, onCancelAbort, onCancelSimulation, onConfirmAbort, onConfirmSimulation } = props;
|
|
1770
|
+
const { abortConfirmation, simulationConfirmation, loading, onCancelAbort, onCancelSimulation, onConfirmAbort, onConfirmSimulation } = props;
|
|
1728
1771
|
const cardRef = (0, react.useRef)(null);
|
|
1729
1772
|
const titleId = (0, react.useId)();
|
|
1730
1773
|
const danger = abortConfirmation;
|
|
1731
1774
|
const open = abortConfirmation || simulationConfirmation;
|
|
1775
|
+
const pending = open && loading;
|
|
1732
1776
|
(0, react.useEffect)(() => {
|
|
1733
1777
|
if (open) cardRef.current?.querySelector("button")?.focus();
|
|
1734
1778
|
}, [open]);
|
|
@@ -1741,40 +1785,56 @@ function EmergencyConfirmation(props) {
|
|
|
1741
1785
|
role: "dialog",
|
|
1742
1786
|
"aria-modal": "true",
|
|
1743
1787
|
"aria-labelledby": titleId,
|
|
1788
|
+
"aria-busy": pending,
|
|
1744
1789
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1745
1790
|
ref: cardRef,
|
|
1746
1791
|
className: cls("emergency-confirmation__card"),
|
|
1747
|
-
children: [
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
}),
|
|
1772
|
-
|
|
1773
|
-
className: cls("emergency-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1792
|
+
children: [
|
|
1793
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1794
|
+
className: cls("emergency-panel__sr-only"),
|
|
1795
|
+
role: "status",
|
|
1796
|
+
"aria-live": "polite",
|
|
1797
|
+
children: pending ? copy.loading : ""
|
|
1798
|
+
}),
|
|
1799
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1800
|
+
className: cls("emergency-confirmation__body"),
|
|
1801
|
+
children: [
|
|
1802
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1803
|
+
className: cls("emergency-confirmation__icon", `emergency-confirmation__icon--${tone}`),
|
|
1804
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: danger ? "alert-triangle" : "info" })
|
|
1805
|
+
}),
|
|
1806
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h5", {
|
|
1807
|
+
className: cls("emergency-confirmation__title"),
|
|
1808
|
+
id: titleId,
|
|
1809
|
+
children: title
|
|
1810
|
+
}),
|
|
1811
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1812
|
+
className: cls("emergency-confirmation__text"),
|
|
1813
|
+
children: text
|
|
1814
|
+
})
|
|
1815
|
+
]
|
|
1816
|
+
}),
|
|
1817
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1818
|
+
className: cls("emergency-confirmation__footer"),
|
|
1819
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1820
|
+
type: "button",
|
|
1821
|
+
className: cls("emergency-confirmation__button", "emergency-confirmation__button--cancel"),
|
|
1822
|
+
disabled: pending,
|
|
1823
|
+
onClick: danger ? onCancelAbort : onCancelSimulation,
|
|
1824
|
+
children: danger ? copy.abortCancel : copy.simulateCancel
|
|
1825
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1826
|
+
type: "button",
|
|
1827
|
+
className: cls("emergency-confirmation__button", danger ? "emergency-confirmation__button--danger" : "emergency-confirmation__button--confirm"),
|
|
1828
|
+
"aria-busy": pending,
|
|
1829
|
+
disabled: pending,
|
|
1830
|
+
onClick: danger ? onConfirmAbort : onConfirmSimulation,
|
|
1831
|
+
children: [pending ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1832
|
+
className: cls("emergency-confirmation__spinner"),
|
|
1833
|
+
"aria-hidden": "true"
|
|
1834
|
+
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: danger ? copy.abortConfirm : copy.simulateConfirm })]
|
|
1835
|
+
})]
|
|
1836
|
+
})
|
|
1837
|
+
]
|
|
1778
1838
|
})
|
|
1779
1839
|
});
|
|
1780
1840
|
}
|
|
@@ -1847,18 +1907,27 @@ function useFocusTrap(ref, active) {
|
|
|
1847
1907
|
}
|
|
1848
1908
|
|
|
1849
1909
|
// src/react/screens/context.tsx
|
|
1910
|
+
const EMPTY_PARAMS = {};
|
|
1850
1911
|
const EmergencyScreenContext = (0, react.createContext)(null);
|
|
1912
|
+
function paramsEqual(first, second) {
|
|
1913
|
+
return first.messageId === second.messageId;
|
|
1914
|
+
}
|
|
1851
1915
|
function EmergencyScreenProvider({ children, initialScreen = "primary" }) {
|
|
1852
1916
|
const [state, setState] = (0, react.useState)({
|
|
1853
1917
|
screen: initialScreen,
|
|
1918
|
+
params: EMPTY_PARAMS,
|
|
1854
1919
|
history: []
|
|
1855
1920
|
});
|
|
1856
|
-
const navigate = (0, react.useCallback)((next) => {
|
|
1921
|
+
const navigate = (0, react.useCallback)((next, params = EMPTY_PARAMS) => {
|
|
1857
1922
|
setState((current) => {
|
|
1858
|
-
if (current.screen === next) return current;
|
|
1923
|
+
if (current.screen === next && paramsEqual(current.params, params)) return current;
|
|
1859
1924
|
return {
|
|
1860
1925
|
screen: next,
|
|
1861
|
-
|
|
1926
|
+
params,
|
|
1927
|
+
history: [...current.history, {
|
|
1928
|
+
screen: current.screen,
|
|
1929
|
+
params: current.params
|
|
1930
|
+
}]
|
|
1862
1931
|
};
|
|
1863
1932
|
});
|
|
1864
1933
|
}, []);
|
|
@@ -1867,31 +1936,47 @@ function EmergencyScreenProvider({ children, initialScreen = "primary" }) {
|
|
|
1867
1936
|
const previous = current.history.at(-1);
|
|
1868
1937
|
if (!previous) return current;
|
|
1869
1938
|
return {
|
|
1870
|
-
screen: previous,
|
|
1939
|
+
screen: previous.screen,
|
|
1940
|
+
params: previous.params,
|
|
1871
1941
|
history: current.history.slice(0, -1)
|
|
1872
1942
|
};
|
|
1873
1943
|
});
|
|
1874
1944
|
}, []);
|
|
1945
|
+
const replace = (0, react.useCallback)((next, params = EMPTY_PARAMS) => {
|
|
1946
|
+
setState((current) => {
|
|
1947
|
+
if (current.screen === next && paramsEqual(current.params, params)) return current;
|
|
1948
|
+
return {
|
|
1949
|
+
screen: next,
|
|
1950
|
+
params,
|
|
1951
|
+
history: current.history.at(-1)?.screen === next ? current.history.slice(0, -1) : current.history
|
|
1952
|
+
};
|
|
1953
|
+
});
|
|
1954
|
+
}, []);
|
|
1875
1955
|
const reset = (0, react.useCallback)(() => {
|
|
1876
1956
|
setState((current) => {
|
|
1877
|
-
if (current.screen === "primary" && current.history.length === 0) return current;
|
|
1957
|
+
if (current.screen === "primary" && current.params.messageId === void 0 && current.history.length === 0) return current;
|
|
1878
1958
|
return {
|
|
1879
1959
|
screen: "primary",
|
|
1960
|
+
params: EMPTY_PARAMS,
|
|
1880
1961
|
history: []
|
|
1881
1962
|
};
|
|
1882
1963
|
});
|
|
1883
1964
|
}, []);
|
|
1884
1965
|
const value = (0, react.useMemo)(() => ({
|
|
1885
1966
|
screen: state.screen,
|
|
1967
|
+
params: state.params,
|
|
1886
1968
|
history: state.history,
|
|
1887
1969
|
navigate,
|
|
1970
|
+
replace,
|
|
1888
1971
|
back,
|
|
1889
1972
|
reset
|
|
1890
1973
|
}), [
|
|
1891
1974
|
back,
|
|
1892
1975
|
navigate,
|
|
1976
|
+
replace,
|
|
1893
1977
|
reset,
|
|
1894
1978
|
state.history,
|
|
1979
|
+
state.params,
|
|
1895
1980
|
state.screen
|
|
1896
1981
|
]);
|
|
1897
1982
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenContext.Provider, {
|
|
@@ -1906,12 +1991,11 @@ function useScreenNavigation() {
|
|
|
1906
1991
|
}
|
|
1907
1992
|
|
|
1908
1993
|
// src/react/components/EmergencyHeader/index.tsx
|
|
1909
|
-
function EmergencyHeader({ titleId, onClose, title = copy.panelLabel, onBack
|
|
1910
|
-
const closeOnly = variant === "close-only";
|
|
1994
|
+
function EmergencyHeader({ titleId, onClose, title = copy.panelLabel, onBack }) {
|
|
1911
1995
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
1912
|
-
className: cls("emergency-panel__header"
|
|
1996
|
+
className: cls("emergency-panel__header"),
|
|
1913
1997
|
children: [
|
|
1914
|
-
onBack
|
|
1998
|
+
onBack ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1915
1999
|
type: "button",
|
|
1916
2000
|
className: cls("emergency-panel__close-button"),
|
|
1917
2001
|
"aria-label": copy.back,
|
|
@@ -1922,7 +2006,7 @@ function EmergencyHeader({ titleId, onClose, title = copy.panelLabel, onBack, va
|
|
|
1922
2006
|
})
|
|
1923
2007
|
}) : null,
|
|
1924
2008
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
1925
|
-
className: cls("emergency-panel__title"
|
|
2009
|
+
className: cls("emergency-panel__title"),
|
|
1926
2010
|
id: titleId,
|
|
1927
2011
|
tabIndex: -1,
|
|
1928
2012
|
children: title
|
|
@@ -1941,6 +2025,245 @@ function EmergencyHeader({ titleId, onClose, title = copy.panelLabel, onBack, va
|
|
|
1941
2025
|
});
|
|
1942
2026
|
}
|
|
1943
2027
|
|
|
2028
|
+
// src/react/screens/primary/data.ts
|
|
2029
|
+
/**
|
|
2030
|
+
* Derives the remaining alert rounds from available messages and designated
|
|
2031
|
+
* responders. Invalid or non-positive input collapses to the minimum of one
|
|
2032
|
+
* round instead of producing `Infinity`, `NaN` or a negative value.
|
|
2033
|
+
*/
|
|
2034
|
+
function calculateRounds(messages, responders) {
|
|
2035
|
+
if (!Number.isFinite(messages) || !Number.isFinite(responders)) return 1;
|
|
2036
|
+
const availableMessages = Math.floor(messages);
|
|
2037
|
+
const designatedResponders = Math.floor(responders);
|
|
2038
|
+
if (availableMessages <= 0 || designatedResponders <= 0) return 1;
|
|
2039
|
+
return Math.max(1, Math.floor(availableMessages / designatedResponders));
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
// src/react/screens/details/copy.ts
|
|
2043
|
+
const detailsCopy = {
|
|
2044
|
+
title: "Detalles",
|
|
2045
|
+
summaryTitle: "Alertas Disponibles",
|
|
2046
|
+
historyTitle: "Historial de alertas",
|
|
2047
|
+
historySubtitle: "Tus últimas alertas y recargas.",
|
|
2048
|
+
historyEmpty: "Todavía no tienes alertas en tu historial.",
|
|
2049
|
+
summaryEmpty: "Todavía no tienes un saldo de alertas disponible.",
|
|
2050
|
+
viewHistory: "Ver historial",
|
|
2051
|
+
messagesSingular: "mensaje de alerta",
|
|
2052
|
+
messagesPlural: "mensajes de alerta",
|
|
2053
|
+
contactsSingular: "contacto de emergencia",
|
|
2054
|
+
contactsPlural: "contactos de emergencia",
|
|
2055
|
+
roundsSingular: "ronda de avisos",
|
|
2056
|
+
roundsPlural: "rondas de avisos"
|
|
2057
|
+
};
|
|
2058
|
+
/** Returns the balance sentence split into text and emphasized number parts. */
|
|
2059
|
+
function alertBalanceParts(messages, responders) {
|
|
2060
|
+
if (!Number.isFinite(messages) || !Number.isFinite(responders) || messages < 0 || responders < 0) return [];
|
|
2061
|
+
const rounds = calculateRounds(messages, responders);
|
|
2062
|
+
const messagesUnit = messages === 1 ? detailsCopy.messagesSingular : detailsCopy.messagesPlural;
|
|
2063
|
+
const contactsUnit = responders === 1 ? detailsCopy.contactsSingular : detailsCopy.contactsPlural;
|
|
2064
|
+
const roundsUnit = rounds === 1 ? detailsCopy.roundsSingular : detailsCopy.roundsPlural;
|
|
2065
|
+
return [
|
|
2066
|
+
messages === 1 ? "Te queda " : "Te quedan ",
|
|
2067
|
+
messages,
|
|
2068
|
+
` ${messagesUnit}, dado que tienes `,
|
|
2069
|
+
responders,
|
|
2070
|
+
` ${contactsUnit}, `,
|
|
2071
|
+
rounds === 1 ? "te queda " : "te quedan ",
|
|
2072
|
+
rounds,
|
|
2073
|
+
` ${roundsUnit}.`
|
|
2074
|
+
];
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
// src/react/screens/details/AlertBalanceSentence.tsx
|
|
2078
|
+
/** Renders the balance sentence with its numbers visually emphasized. */
|
|
2079
|
+
function AlertBalanceSentence({ messages, responders }) {
|
|
2080
|
+
const parts = alertBalanceParts(messages, responders);
|
|
2081
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: parts.map((part, index) => typeof part === "number" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: part }, index) : part) });
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
// src/core/date.ts
|
|
2085
|
+
/**
|
|
2086
|
+
* Formats a timestamp as `h:mmam` or `h:mmpm` in local time, without a space
|
|
2087
|
+
* or leading zero.
|
|
2088
|
+
*/
|
|
2089
|
+
function formatShortTime(value, locale = "es") {
|
|
2090
|
+
if (value === null) return "";
|
|
2091
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
2092
|
+
if (Number.isNaN(date.getTime())) return "";
|
|
2093
|
+
const parts = new Intl.DateTimeFormat(locale, {
|
|
2094
|
+
hour: "numeric",
|
|
2095
|
+
minute: "2-digit",
|
|
2096
|
+
hour12: true
|
|
2097
|
+
}).formatToParts(date);
|
|
2098
|
+
const hour = parts.find((part) => part.type === "hour")?.value;
|
|
2099
|
+
const minute = parts.find((part) => part.type === "minute")?.value;
|
|
2100
|
+
const dayPeriod = parts.find((part) => part.type === "dayPeriod")?.value;
|
|
2101
|
+
if (!hour || !minute) return "";
|
|
2102
|
+
return `${hour}:${minute}${normalizePeriod(dayPeriod)}`;
|
|
2103
|
+
}
|
|
2104
|
+
/**
|
|
2105
|
+
* Formats a timestamp as `dd/mm/yyyy hh:mm` in local time, using 24-hour time
|
|
2106
|
+
* and zero-padded parts. Missing or invalid input collapses to an empty string.
|
|
2107
|
+
*/
|
|
2108
|
+
function formatDateTime(value, locale = "es") {
|
|
2109
|
+
if (value === null) return "";
|
|
2110
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
2111
|
+
if (Number.isNaN(date.getTime())) return "";
|
|
2112
|
+
const parts = new Intl.DateTimeFormat(locale, {
|
|
2113
|
+
day: "2-digit",
|
|
2114
|
+
month: "2-digit",
|
|
2115
|
+
year: "numeric",
|
|
2116
|
+
hour: "2-digit",
|
|
2117
|
+
minute: "2-digit",
|
|
2118
|
+
hourCycle: "h23"
|
|
2119
|
+
}).formatToParts(date);
|
|
2120
|
+
const day = parts.find((part) => part.type === "day")?.value;
|
|
2121
|
+
const month = parts.find((part) => part.type === "month")?.value;
|
|
2122
|
+
const year = parts.find((part) => part.type === "year")?.value;
|
|
2123
|
+
const hour = parts.find((part) => part.type === "hour")?.value;
|
|
2124
|
+
const minute = parts.find((part) => part.type === "minute")?.value;
|
|
2125
|
+
if (!day || !month || !year || !hour || !minute) return "";
|
|
2126
|
+
return `${day}/${month}/${year} ${hour}:${minute}`;
|
|
2127
|
+
}
|
|
2128
|
+
function normalizePeriod(dayPeriod) {
|
|
2129
|
+
if (!dayPeriod) return "";
|
|
2130
|
+
if (/^p/i.test(dayPeriod)) return "pm";
|
|
2131
|
+
return /^a/i.test(dayPeriod) ? "am" : "";
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
// src/react/screens/history/copy.ts
|
|
2135
|
+
const historyCopy = {
|
|
2136
|
+
title: "Historial de alertas",
|
|
2137
|
+
subtitle: "Consulta tus alertas, pruebas y recargas.",
|
|
2138
|
+
empty: "Todavía no tienes alertas en tu historial.",
|
|
2139
|
+
showMore: "Ver más",
|
|
2140
|
+
kindLabels: {
|
|
2141
|
+
emergency: "Emergencia",
|
|
2142
|
+
test: "Prueba",
|
|
2143
|
+
recharge: "Recarga"
|
|
2144
|
+
}
|
|
2145
|
+
};
|
|
2146
|
+
function formatContacts(count) {
|
|
2147
|
+
return count === 1 ? "1 contacto" : `${count} contactos`;
|
|
2148
|
+
}
|
|
2149
|
+
function formatMessages(count) {
|
|
2150
|
+
return count === 1 ? "1 mensaje" : `${count} mensajes`;
|
|
2151
|
+
}
|
|
2152
|
+
function formatHistoryRounds(count) {
|
|
2153
|
+
return count === 1 ? "1 ronda" : `${count} rondas`;
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
// src/react/screens/history/data.ts
|
|
2157
|
+
/** Local demo alert history, newest first. */
|
|
2158
|
+
const DEMO_ALERT_HISTORY = [
|
|
2159
|
+
{
|
|
2160
|
+
id: "alert-2026-10-31",
|
|
2161
|
+
kind: "emergency",
|
|
2162
|
+
occurredAt: "2026-10-31T13:50:00",
|
|
2163
|
+
responders: 3,
|
|
2164
|
+
remainingMessages: 27
|
|
2165
|
+
},
|
|
2166
|
+
{
|
|
2167
|
+
id: "alert-2026-10-20",
|
|
2168
|
+
kind: "recharge",
|
|
2169
|
+
occurredAt: "2026-10-20T09:15:00",
|
|
2170
|
+
responders: 3,
|
|
2171
|
+
remainingMessages: 100
|
|
2172
|
+
},
|
|
2173
|
+
{
|
|
2174
|
+
id: "alert-2026-10-12",
|
|
2175
|
+
kind: "test",
|
|
2176
|
+
occurredAt: "2026-10-12T18:40:00",
|
|
2177
|
+
responders: 3,
|
|
2178
|
+
remainingMessages: 73
|
|
2179
|
+
},
|
|
2180
|
+
{
|
|
2181
|
+
id: "alert-2026-10-01",
|
|
2182
|
+
kind: "recharge",
|
|
2183
|
+
occurredAt: "2026-10-01T11:05:00",
|
|
2184
|
+
responders: 3,
|
|
2185
|
+
remainingMessages: 76
|
|
2186
|
+
}
|
|
2187
|
+
];
|
|
2188
|
+
/** Returns a newest-first copy of the history. */
|
|
2189
|
+
function sortedHistoryEntries(entries = DEMO_ALERT_HISTORY) {
|
|
2190
|
+
return [...entries].sort((first, second) => new Date(second.occurredAt).getTime() - new Date(first.occurredAt).getTime());
|
|
2191
|
+
}
|
|
2192
|
+
/** Newest entry, used as the source of the current balance. */
|
|
2193
|
+
function latestHistoryEntry(entries = DEMO_ALERT_HISTORY) {
|
|
2194
|
+
return sortedHistoryEntries(entries)[0] ?? null;
|
|
2195
|
+
}
|
|
2196
|
+
/** Resolves a single history entry by id. */
|
|
2197
|
+
function historyEntryById(id, entries = DEMO_ALERT_HISTORY) {
|
|
2198
|
+
if (!id) return null;
|
|
2199
|
+
return entries.find((entry) => entry.id === id) ?? null;
|
|
2200
|
+
}
|
|
2201
|
+
/** Rounds represented by the entry's remaining messages and responders. */
|
|
2202
|
+
function historyEntryRounds(entry) {
|
|
2203
|
+
return calculateRounds(entry.remainingMessages, entry.responders);
|
|
2204
|
+
}
|
|
2205
|
+
/** Current balance derived from the newest history entry. */
|
|
2206
|
+
function currentAlertBalance(entries = DEMO_ALERT_HISTORY) {
|
|
2207
|
+
const latest = latestHistoryEntry(entries);
|
|
2208
|
+
if (!latest) return null;
|
|
2209
|
+
return {
|
|
2210
|
+
messages: latest.remainingMessages,
|
|
2211
|
+
responders: latest.responders,
|
|
2212
|
+
rounds: historyEntryRounds(latest)
|
|
2213
|
+
};
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
// src/react/screens/history/AlertHistoryRow.tsx
|
|
2217
|
+
/** Reusable history row. The whole row is a button with a full text label. */
|
|
2218
|
+
function AlertHistoryRow({ entry, locale, onOpen }) {
|
|
2219
|
+
const occurredAt = formatDateTime(entry.occurredAt, locale);
|
|
2220
|
+
const rounds = historyEntryRounds(entry);
|
|
2221
|
+
const label = [
|
|
2222
|
+
historyCopy.kindLabels[entry.kind],
|
|
2223
|
+
occurredAt,
|
|
2224
|
+
formatContacts(entry.responders),
|
|
2225
|
+
`${formatMessages(entry.remainingMessages)} restantes`,
|
|
2226
|
+
`${formatHistoryRounds(rounds)} de avisos`
|
|
2227
|
+
].join(", ");
|
|
2228
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
2229
|
+
className: cls("emergency-alert-row"),
|
|
2230
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2231
|
+
type: "button",
|
|
2232
|
+
className: cls("emergency-alert-row__button"),
|
|
2233
|
+
"aria-label": label,
|
|
2234
|
+
onClick: () => onOpen(entry.id),
|
|
2235
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2236
|
+
className: cls("emergency-alert-row__top"),
|
|
2237
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("time", {
|
|
2238
|
+
className: cls("emergency-alert-row__time"),
|
|
2239
|
+
dateTime: entry.occurredAt,
|
|
2240
|
+
children: occurredAt
|
|
2241
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2242
|
+
className: cls("emergency-alert-row__badge"),
|
|
2243
|
+
"data-kind": entry.kind,
|
|
2244
|
+
children: historyCopy.kindLabels[entry.kind]
|
|
2245
|
+
})]
|
|
2246
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2247
|
+
className: cls("emergency-alert-row__stats"),
|
|
2248
|
+
children: [
|
|
2249
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2250
|
+
className: cls("emergency-alert-row__stat"),
|
|
2251
|
+
children: formatContacts(entry.responders)
|
|
2252
|
+
}),
|
|
2253
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2254
|
+
className: cls("emergency-alert-row__stat"),
|
|
2255
|
+
children: formatMessages(entry.remainingMessages)
|
|
2256
|
+
}),
|
|
2257
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2258
|
+
className: cls("emergency-alert-row__stat"),
|
|
2259
|
+
children: formatHistoryRounds(rounds)
|
|
2260
|
+
})
|
|
2261
|
+
]
|
|
2262
|
+
})]
|
|
2263
|
+
})
|
|
2264
|
+
});
|
|
2265
|
+
}
|
|
2266
|
+
|
|
1944
2267
|
// src/react/screens/layout.tsx
|
|
1945
2268
|
/**
|
|
1946
2269
|
* Shared shell for every screen. The wrapper is `display: contents` so the
|
|
@@ -1962,46 +2285,254 @@ function EmergencyScreenLayout({ screen, titleId, header, footer, children, body
|
|
|
1962
2285
|
});
|
|
1963
2286
|
}
|
|
1964
2287
|
|
|
1965
|
-
// src/react/screens/
|
|
1966
|
-
|
|
1967
|
-
|
|
2288
|
+
// src/react/screens/details/index.tsx
|
|
2289
|
+
function DetailsScreen({ titleId, locale, onClose }) {
|
|
2290
|
+
const { back, navigate } = useScreenNavigation();
|
|
2291
|
+
const balance = currentAlertBalance();
|
|
2292
|
+
const preview = sortedHistoryEntries().slice(0, 2);
|
|
1968
2293
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenLayout, {
|
|
1969
|
-
screen,
|
|
2294
|
+
screen: "details",
|
|
1970
2295
|
titleId,
|
|
2296
|
+
bodyClassName: "emergency-panel__body--details",
|
|
1971
2297
|
header: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyHeader, {
|
|
1972
2298
|
titleId,
|
|
1973
|
-
title,
|
|
1974
|
-
onClose,
|
|
1975
|
-
onBack
|
|
2299
|
+
title: detailsCopy.title,
|
|
2300
|
+
onClose: () => onClose("close"),
|
|
2301
|
+
onBack: back
|
|
1976
2302
|
}),
|
|
1977
2303
|
footer: null,
|
|
1978
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1979
|
-
className: cls("emergency-
|
|
1980
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
1981
|
-
className: cls("emergency-
|
|
1982
|
-
|
|
1983
|
-
|
|
2304
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2305
|
+
className: cls("emergency-details"),
|
|
2306
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
2307
|
+
className: cls("emergency-details__section"),
|
|
2308
|
+
"aria-label": detailsCopy.summaryTitle,
|
|
2309
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2310
|
+
className: cls("emergency-details__card"),
|
|
2311
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2312
|
+
className: cls("emergency-details__card-header"),
|
|
2313
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
2314
|
+
className: cls("emergency-details__card-title"),
|
|
2315
|
+
children: detailsCopy.summaryTitle
|
|
2316
|
+
})
|
|
2317
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2318
|
+
className: cls("emergency-details__card-body"),
|
|
2319
|
+
children: balance ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2320
|
+
className: cls("emergency-details__balance"),
|
|
2321
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AlertBalanceSentence, {
|
|
2322
|
+
messages: balance.messages,
|
|
2323
|
+
responders: balance.responders
|
|
2324
|
+
})
|
|
2325
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2326
|
+
className: cls("emergency-details__empty"),
|
|
2327
|
+
children: detailsCopy.summaryEmpty
|
|
2328
|
+
})
|
|
2329
|
+
})]
|
|
2330
|
+
})
|
|
2331
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2332
|
+
className: cls("emergency-details__section"),
|
|
2333
|
+
"aria-label": detailsCopy.historyTitle,
|
|
2334
|
+
children: [
|
|
2335
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2336
|
+
className: cls("emergency-details__heading"),
|
|
2337
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
2338
|
+
className: cls("emergency-details__title"),
|
|
2339
|
+
children: detailsCopy.historyTitle
|
|
2340
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2341
|
+
className: cls("emergency-details__subtitle"),
|
|
2342
|
+
children: detailsCopy.historySubtitle
|
|
2343
|
+
})]
|
|
2344
|
+
}),
|
|
2345
|
+
preview.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
2346
|
+
className: cls("emergency-details__list"),
|
|
2347
|
+
children: preview.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AlertHistoryRow, {
|
|
2348
|
+
entry,
|
|
2349
|
+
locale,
|
|
2350
|
+
onOpen: (messageId) => navigate("message-detail", { messageId })
|
|
2351
|
+
}, entry.id))
|
|
2352
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2353
|
+
className: cls("emergency-details__empty"),
|
|
2354
|
+
children: detailsCopy.historyEmpty
|
|
2355
|
+
}),
|
|
2356
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2357
|
+
type: "button",
|
|
2358
|
+
className: cls("emergency-details__history-button"),
|
|
2359
|
+
onClick: () => navigate("history"),
|
|
2360
|
+
children: detailsCopy.viewHistory
|
|
2361
|
+
})
|
|
2362
|
+
]
|
|
2363
|
+
})]
|
|
1984
2364
|
})
|
|
1985
2365
|
});
|
|
1986
2366
|
}
|
|
1987
2367
|
|
|
1988
|
-
// src/react/screens/
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
2368
|
+
// src/react/screens/history/index.tsx
|
|
2369
|
+
function HistoryScreen({ titleId, locale, onClose }) {
|
|
2370
|
+
const { back, navigate } = useScreenNavigation();
|
|
2371
|
+
const listId = (0, react.useId)();
|
|
2372
|
+
const [expanded, setExpanded] = (0, react.useState)(false);
|
|
2373
|
+
const entries = sortedHistoryEntries();
|
|
2374
|
+
const hiddenCount = Math.max(0, entries.length - 2);
|
|
2375
|
+
const visibleEntries = expanded ? entries : entries.slice(0, 2);
|
|
2376
|
+
const showMore = hiddenCount > 0 && !expanded;
|
|
2377
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenLayout, {
|
|
2378
|
+
screen: "history",
|
|
2379
|
+
titleId,
|
|
2380
|
+
bodyClassName: "emergency-panel__body--history",
|
|
2381
|
+
header: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyHeader, {
|
|
2382
|
+
titleId,
|
|
2383
|
+
title: historyCopy.title,
|
|
2384
|
+
onClose: () => onClose("close"),
|
|
2385
|
+
onBack: back
|
|
2386
|
+
}),
|
|
2387
|
+
footer: null,
|
|
2388
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2389
|
+
className: cls("emergency-history"),
|
|
2390
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2391
|
+
className: cls("emergency-history__heading"),
|
|
2392
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
2393
|
+
className: cls("emergency-history__title"),
|
|
2394
|
+
children: historyCopy.title
|
|
2395
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2396
|
+
className: cls("emergency-history__subtitle"),
|
|
2397
|
+
children: historyCopy.subtitle
|
|
2398
|
+
})]
|
|
2399
|
+
}), entries.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
2400
|
+
className: cls("emergency-history__list"),
|
|
2401
|
+
id: listId,
|
|
2402
|
+
children: visibleEntries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AlertHistoryRow, {
|
|
2403
|
+
entry,
|
|
2404
|
+
locale,
|
|
2405
|
+
onOpen: (messageId) => navigate("message-detail", { messageId })
|
|
2406
|
+
}, entry.id))
|
|
2407
|
+
}), showMore ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2408
|
+
type: "button",
|
|
2409
|
+
className: cls("emergency-history__more"),
|
|
2410
|
+
"aria-expanded": expanded,
|
|
2411
|
+
"aria-controls": listId,
|
|
2412
|
+
onClick: () => setExpanded(true),
|
|
2413
|
+
children: historyCopy.showMore
|
|
2414
|
+
}) : null] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2415
|
+
className: cls("emergency-history__empty"),
|
|
2416
|
+
children: historyCopy.empty
|
|
2417
|
+
})]
|
|
2418
|
+
})
|
|
2419
|
+
});
|
|
2420
|
+
}
|
|
1993
2421
|
|
|
1994
|
-
// src/react/screens/
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
2422
|
+
// src/react/screens/message-detail/index.tsx
|
|
2423
|
+
const messageDetailCopy = {
|
|
2424
|
+
title: "Detalle del mensaje",
|
|
2425
|
+
dateTime: "Fecha y hora",
|
|
2426
|
+
kind: "Tipo",
|
|
2427
|
+
responders: "Contactos de emergencia",
|
|
2428
|
+
messages: "Mensajes restantes",
|
|
2429
|
+
rounds: "Rondas de avisos",
|
|
2430
|
+
notFound: "No encontramos este mensaje. Puede que ya no esté disponible en tu historial.",
|
|
2431
|
+
backToHistory: "Volver al historial"
|
|
2432
|
+
};
|
|
2433
|
+
function MessageDetailScreen({ titleId, locale, onClose }) {
|
|
2434
|
+
const { back, params, replace } = useScreenNavigation();
|
|
2435
|
+
const entry = historyEntryById(params.messageId);
|
|
2436
|
+
const header = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyHeader, {
|
|
1999
2437
|
titleId,
|
|
2000
|
-
title:
|
|
2001
|
-
description: detailsCopy.description,
|
|
2438
|
+
title: messageDetailCopy.title,
|
|
2002
2439
|
onClose: () => onClose("close"),
|
|
2003
2440
|
onBack: back
|
|
2004
2441
|
});
|
|
2442
|
+
if (!entry) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenLayout, {
|
|
2443
|
+
screen: "message-detail",
|
|
2444
|
+
titleId,
|
|
2445
|
+
bodyClassName: "emergency-panel__body--message-detail",
|
|
2446
|
+
header,
|
|
2447
|
+
footer: null,
|
|
2448
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2449
|
+
className: cls("emergency-message-detail__empty"),
|
|
2450
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2451
|
+
className: cls("emergency-message-detail__empty-text"),
|
|
2452
|
+
children: messageDetailCopy.notFound
|
|
2453
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2454
|
+
type: "button",
|
|
2455
|
+
className: cls("emergency-message-detail__back"),
|
|
2456
|
+
onClick: () => replace("history"),
|
|
2457
|
+
children: messageDetailCopy.backToHistory
|
|
2458
|
+
})]
|
|
2459
|
+
})
|
|
2460
|
+
});
|
|
2461
|
+
const rounds = historyEntryRounds(entry);
|
|
2462
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyScreenLayout, {
|
|
2463
|
+
screen: "message-detail",
|
|
2464
|
+
titleId,
|
|
2465
|
+
bodyClassName: "emergency-panel__body--message-detail",
|
|
2466
|
+
header,
|
|
2467
|
+
footer: null,
|
|
2468
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2469
|
+
className: cls("emergency-message-detail"),
|
|
2470
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2471
|
+
className: cls("emergency-message-detail__card"),
|
|
2472
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2473
|
+
className: cls("emergency-message-detail__card-header"),
|
|
2474
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
2475
|
+
className: cls("emergency-message-detail__card-title"),
|
|
2476
|
+
children: messageDetailCopy.title
|
|
2477
|
+
})
|
|
2478
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
|
|
2479
|
+
className: cls("emergency-message-detail__list"),
|
|
2480
|
+
children: [
|
|
2481
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2482
|
+
className: cls("emergency-message-detail__row"),
|
|
2483
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
|
|
2484
|
+
className: cls("emergency-message-detail__label"),
|
|
2485
|
+
children: messageDetailCopy.dateTime
|
|
2486
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
|
|
2487
|
+
className: cls("emergency-message-detail__value"),
|
|
2488
|
+
children: formatDateTime(entry.occurredAt, locale)
|
|
2489
|
+
})]
|
|
2490
|
+
}),
|
|
2491
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2492
|
+
className: cls("emergency-message-detail__row"),
|
|
2493
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
|
|
2494
|
+
className: cls("emergency-message-detail__label"),
|
|
2495
|
+
children: messageDetailCopy.kind
|
|
2496
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
|
|
2497
|
+
className: cls("emergency-message-detail__value"),
|
|
2498
|
+
children: historyCopy.kindLabels[entry.kind]
|
|
2499
|
+
})]
|
|
2500
|
+
}),
|
|
2501
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2502
|
+
className: cls("emergency-message-detail__row"),
|
|
2503
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
|
|
2504
|
+
className: cls("emergency-message-detail__label"),
|
|
2505
|
+
children: messageDetailCopy.responders
|
|
2506
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
|
|
2507
|
+
className: cls("emergency-message-detail__value"),
|
|
2508
|
+
children: formatContacts(entry.responders)
|
|
2509
|
+
})]
|
|
2510
|
+
}),
|
|
2511
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2512
|
+
className: cls("emergency-message-detail__row"),
|
|
2513
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
|
|
2514
|
+
className: cls("emergency-message-detail__label"),
|
|
2515
|
+
children: messageDetailCopy.messages
|
|
2516
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
|
|
2517
|
+
className: cls("emergency-message-detail__value"),
|
|
2518
|
+
children: formatMessages(entry.remainingMessages)
|
|
2519
|
+
})]
|
|
2520
|
+
}),
|
|
2521
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2522
|
+
className: cls("emergency-message-detail__row"),
|
|
2523
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
|
|
2524
|
+
className: cls("emergency-message-detail__label"),
|
|
2525
|
+
children: messageDetailCopy.rounds
|
|
2526
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
|
|
2527
|
+
className: cls("emergency-message-detail__value"),
|
|
2528
|
+
children: formatHistoryRounds(rounds)
|
|
2529
|
+
})]
|
|
2530
|
+
})
|
|
2531
|
+
]
|
|
2532
|
+
})]
|
|
2533
|
+
})
|
|
2534
|
+
})
|
|
2535
|
+
});
|
|
2005
2536
|
}
|
|
2006
2537
|
|
|
2007
2538
|
// src/core/phone.ts
|
|
@@ -2026,9 +2557,9 @@ function toVenezuelanE164(input) {
|
|
|
2026
2557
|
|
|
2027
2558
|
// src/react/components/EmergencyFooter/index.tsx
|
|
2028
2559
|
function EmergencyFooter(props) {
|
|
2029
|
-
const { active, busy, aborting, feedbackVisible, showSimulation = true, emergencyDisabled = false, onActivate, onRequestAbort, onRequestSimulation } = props;
|
|
2560
|
+
const { active, busy, aborting, feedbackVisible, showSimulation = true, emergencyDisabled = false, square = false, onActivate, onRequestAbort, onRequestSimulation } = props;
|
|
2030
2561
|
if (active) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("footer", {
|
|
2031
|
-
className: cls("emergency-panel__footer"),
|
|
2562
|
+
className: cls("emergency-panel__footer", square && "emergency-panel__footer--square"),
|
|
2032
2563
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2033
2564
|
type: "button",
|
|
2034
2565
|
className: cls("emergency-status__abort-btn"),
|
|
@@ -2039,7 +2570,7 @@ function EmergencyFooter(props) {
|
|
|
2039
2570
|
})
|
|
2040
2571
|
});
|
|
2041
2572
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
|
|
2042
|
-
className: cls("emergency-panel__footer", !showSimulation && "emergency-panel__footer--single"),
|
|
2573
|
+
className: cls("emergency-panel__footer", !showSimulation && "emergency-panel__footer--single", square && "emergency-panel__footer--square"),
|
|
2043
2574
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2044
2575
|
type: "button",
|
|
2045
2576
|
className: cls("emergency-panel__button", "emergency-panel__button--emergency"),
|
|
@@ -2314,9 +2845,9 @@ function EmergencyError({ error, onRetry }) {
|
|
|
2314
2845
|
|
|
2315
2846
|
// src/react/components/EmergencyFeedback/index.tsx
|
|
2316
2847
|
/** Time the feedback stays on screen before dismissing itself. */
|
|
2317
|
-
const FEEDBACK_AUTO_DISMISS_MS =
|
|
2848
|
+
const FEEDBACK_AUTO_DISMISS_MS = 12e3;
|
|
2318
2849
|
/** Time the geolocation feedback stays on screen before dismissing itself. */
|
|
2319
|
-
const GEOLOCATION_FEEDBACK_AUTO_DISMISS_MS =
|
|
2850
|
+
const GEOLOCATION_FEEDBACK_AUTO_DISMISS_MS = 12e3;
|
|
2320
2851
|
function geolocationText(error) {
|
|
2321
2852
|
switch (error.code) {
|
|
2322
2853
|
case "geolocation_denied": return copy.geolocationDenied;
|
|
@@ -2402,32 +2933,6 @@ function EmergencyLoading() {
|
|
|
2402
2933
|
});
|
|
2403
2934
|
}
|
|
2404
2935
|
|
|
2405
|
-
// src/core/date.ts
|
|
2406
|
-
/**
|
|
2407
|
-
* Formats a timestamp as `h:mmam` or `h:mmpm` in local time, without a space
|
|
2408
|
-
* or leading zero.
|
|
2409
|
-
*/
|
|
2410
|
-
function formatShortTime(value, locale = "es") {
|
|
2411
|
-
if (value === null) return "";
|
|
2412
|
-
const date = value instanceof Date ? value : new Date(value);
|
|
2413
|
-
if (Number.isNaN(date.getTime())) return "";
|
|
2414
|
-
const parts = new Intl.DateTimeFormat(locale, {
|
|
2415
|
-
hour: "numeric",
|
|
2416
|
-
minute: "2-digit",
|
|
2417
|
-
hour12: true
|
|
2418
|
-
}).formatToParts(date);
|
|
2419
|
-
const hour = parts.find((part) => part.type === "hour")?.value;
|
|
2420
|
-
const minute = parts.find((part) => part.type === "minute")?.value;
|
|
2421
|
-
const dayPeriod = parts.find((part) => part.type === "dayPeriod")?.value;
|
|
2422
|
-
if (!hour || !minute) return "";
|
|
2423
|
-
return `${hour}:${minute}${normalizePeriod(dayPeriod)}`;
|
|
2424
|
-
}
|
|
2425
|
-
function normalizePeriod(dayPeriod) {
|
|
2426
|
-
if (!dayPeriod) return "";
|
|
2427
|
-
if (/^p/i.test(dayPeriod)) return "pm";
|
|
2428
|
-
return /^a/i.test(dayPeriod) ? "am" : "";
|
|
2429
|
-
}
|
|
2430
|
-
|
|
2431
2936
|
// src/react/components/StatusIcon/index.tsx
|
|
2432
2937
|
/**
|
|
2433
2938
|
* Explicit responder state model:
|
|
@@ -2588,23 +3093,9 @@ function formatRounds(rounds) {
|
|
|
2588
3093
|
return `${rounds} ${roundsUnit(rounds)}`;
|
|
2589
3094
|
}
|
|
2590
3095
|
|
|
2591
|
-
// src/react/screens/primary/data.ts
|
|
2592
|
-
/**
|
|
2593
|
-
* Derives the remaining alert rounds from available messages and designated
|
|
2594
|
-
* responders. Invalid or non-positive input collapses to the minimum of one
|
|
2595
|
-
* round instead of producing `Infinity`, `NaN` or a negative value.
|
|
2596
|
-
*/
|
|
2597
|
-
function calculateRounds(messages, responders) {
|
|
2598
|
-
if (!Number.isFinite(messages) || !Number.isFinite(responders)) return 1;
|
|
2599
|
-
const availableMessages = Math.floor(messages);
|
|
2600
|
-
const designatedResponders = Math.floor(responders);
|
|
2601
|
-
if (availableMessages <= 0 || designatedResponders <= 0) return 1;
|
|
2602
|
-
return Math.max(1, Math.floor(availableMessages / designatedResponders));
|
|
2603
|
-
}
|
|
2604
|
-
|
|
2605
3096
|
// src/react/screens/primary/Availability.tsx
|
|
2606
3097
|
function Availability({ onRecharge, onDetails }) {
|
|
2607
|
-
const rounds = calculateRounds(
|
|
3098
|
+
const rounds = calculateRounds(27, 3);
|
|
2608
3099
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2609
3100
|
className: cls("emergency-availability"),
|
|
2610
3101
|
"aria-label": primaryCopy.availabilityTitle,
|
|
@@ -2646,23 +3137,30 @@ function Availability({ onRecharge, onDetails }) {
|
|
|
2646
3137
|
|
|
2647
3138
|
// src/react/components/EmergencyBody/index.tsx
|
|
2648
3139
|
function EmergencyBody(props) {
|
|
2649
|
-
const { active, responders, refreshing, loading, locale, geolocationError, activationError, error, abortResult, simulationResult, onRefresh, onRetry, onDismissFeedback, onRecharge, onDetails } = props;
|
|
3140
|
+
const { active, responders, refreshing, loading, locale, geolocationError, activationError, error, abortResult, simulationResult, onRefresh, onRetry, onDismissFeedback, onRecharge, onDetails, onClose } = props;
|
|
2650
3141
|
const showingFeedback = geolocationError !== null || Boolean(activationError) || abortResult !== null || simulationResult !== null;
|
|
2651
3142
|
const idle = !active && !loading && !showingFeedback && error === null;
|
|
2652
3143
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2653
3144
|
className: cls("emergency-panel__body-split", idle && "emergency-panel__body-split--with-availability"),
|
|
2654
|
-
children: [idle ? /* @__PURE__ */ (0, react_jsx_runtime.
|
|
3145
|
+
children: [idle ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2655
3146
|
className: cls("emergency-panel__availability-region"),
|
|
2656
3147
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Availability, {
|
|
2657
3148
|
onRecharge,
|
|
2658
3149
|
onDetails
|
|
2659
3150
|
})
|
|
2660
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
2661
|
-
className: cls("emergency-
|
|
2662
|
-
"aria-hidden": "true"
|
|
2663
|
-
})] }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2664
|
-
className: cls("emergency-panel__prompt-region"),
|
|
3151
|
+
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3152
|
+
className: cls("emergency-panel__prompt-region", idle && "emergency-panel__prompt-region--idle"),
|
|
2665
3153
|
children: [
|
|
3154
|
+
idle ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3155
|
+
type: "button",
|
|
3156
|
+
className: cls("emergency-panel__close-button", "emergency-panel__close-button--prompt"),
|
|
3157
|
+
"aria-label": copy.close,
|
|
3158
|
+
onClick: onClose,
|
|
3159
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
3160
|
+
name: "close",
|
|
3161
|
+
className: cls("emergency-panel__close-icon")
|
|
3162
|
+
})
|
|
3163
|
+
}) : null,
|
|
2666
3164
|
!loading && !showingFeedback ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2667
3165
|
className: cls("sos-img__container"),
|
|
2668
3166
|
id: "sos-img__container",
|
|
@@ -2706,12 +3204,12 @@ function PrimaryScreen(props) {
|
|
|
2706
3204
|
const { navigate } = useScreenNavigation();
|
|
2707
3205
|
const idle = !props.active && !props.loading && !props.feedbackVisible && props.error === null;
|
|
2708
3206
|
const needsContactPhone = props.contactPhone === null;
|
|
2709
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.
|
|
3207
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(EmergencyScreenLayout, {
|
|
2710
3208
|
screen: "primary",
|
|
2711
3209
|
titleId: props.titleId,
|
|
2712
|
-
|
|
3210
|
+
bodyClassName: idle ? "emergency-panel__body--primary" : void 0,
|
|
3211
|
+
header: idle ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyHeader, {
|
|
2713
3212
|
titleId: props.titleId,
|
|
2714
|
-
variant: idle ? "close-only" : "default",
|
|
2715
3213
|
onClose: () => props.onClose("close")
|
|
2716
3214
|
}),
|
|
2717
3215
|
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyFooter, {
|
|
@@ -2719,11 +3217,17 @@ function PrimaryScreen(props) {
|
|
|
2719
3217
|
busy: props.busy,
|
|
2720
3218
|
aborting: props.aborting,
|
|
2721
3219
|
feedbackVisible: props.feedbackVisible,
|
|
3220
|
+
square: idle,
|
|
2722
3221
|
onActivate: needsContactPhone ? () => navigate("pre-emergency") : props.onActivate,
|
|
2723
3222
|
onRequestAbort: props.onRequestAbort,
|
|
2724
3223
|
onRequestSimulation: props.onRequestSimulation
|
|
2725
3224
|
}),
|
|
2726
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
3225
|
+
children: [idle ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
3226
|
+
className: cls("emergency-panel__sr-only"),
|
|
3227
|
+
id: props.titleId,
|
|
3228
|
+
tabIndex: -1,
|
|
3229
|
+
children: copy.panelLabel
|
|
3230
|
+
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyBody, {
|
|
2727
3231
|
active: props.active,
|
|
2728
3232
|
responders: props.responders,
|
|
2729
3233
|
refreshing: props.refreshing,
|
|
@@ -2738,8 +3242,9 @@ function PrimaryScreen(props) {
|
|
|
2738
3242
|
onRetry: props.onRetry,
|
|
2739
3243
|
onDismissFeedback: props.onDismissFeedback,
|
|
2740
3244
|
onRecharge: () => navigate("pricing"),
|
|
2741
|
-
onDetails: () => navigate("details")
|
|
2742
|
-
|
|
3245
|
+
onDetails: () => navigate("details"),
|
|
3246
|
+
onClose: () => props.onClose("close")
|
|
3247
|
+
})]
|
|
2743
3248
|
});
|
|
2744
3249
|
}
|
|
2745
3250
|
|
|
@@ -2912,7 +3417,9 @@ const screenRegistry = {
|
|
|
2912
3417
|
primary: PrimaryScreen,
|
|
2913
3418
|
"pre-emergency": PreEmergencyScreen,
|
|
2914
3419
|
pricing: PricingScreen,
|
|
2915
|
-
details: DetailsScreen
|
|
3420
|
+
details: DetailsScreen,
|
|
3421
|
+
history: HistoryScreen,
|
|
3422
|
+
"message-detail": MessageDetailScreen
|
|
2916
3423
|
};
|
|
2917
3424
|
|
|
2918
3425
|
// src/react/components/EmergencyPanel/index.tsx
|
|
@@ -2941,7 +3448,7 @@ function ActiveScreen({ screenProps, open, forcePrimary }) {
|
|
|
2941
3448
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Screen, { ...screenProps });
|
|
2942
3449
|
}
|
|
2943
3450
|
function EmergencyPanel(props) {
|
|
2944
|
-
const { open, theme, active, busy, aborting, loading, locale, responders, refreshing, geolocationError, contactPhone, activationError, error, abortResult, simulationResult, abortConfirmation, simulationConfirmation, onClose, onActivate, onRefresh, onRetry, onDismissFeedback, onRequestAbort, onRequestSimulation, onCancelAbort, onCancelSimulation, onConfirmAbort, onConfirmSimulation, onSetContactPhone } = props;
|
|
3451
|
+
const { open, theme, active, busy, aborting, simulating, loading, locale, responders, refreshing, geolocationError, contactPhone, activationError, error, abortResult, simulationResult, abortConfirmation, simulationConfirmation, onClose, onActivate, onRefresh, onRetry, onDismissFeedback, onRequestAbort, onRequestSimulation, onCancelAbort, onCancelSimulation, onConfirmAbort, onConfirmSimulation, onSetContactPhone } = props;
|
|
2945
3452
|
const titleId = (0, react.useId)();
|
|
2946
3453
|
const dialogRef = (0, react.useRef)(null);
|
|
2947
3454
|
const feedbackVisible = geolocationError !== null || Boolean(activationError) || abortResult !== null || simulationResult !== null;
|
|
@@ -2993,6 +3500,7 @@ function EmergencyPanel(props) {
|
|
|
2993
3500
|
}) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmergencyConfirmation, {
|
|
2994
3501
|
abortConfirmation,
|
|
2995
3502
|
simulationConfirmation,
|
|
3503
|
+
loading: abortConfirmation ? aborting : simulating,
|
|
2996
3504
|
onCancelAbort,
|
|
2997
3505
|
onCancelSimulation,
|
|
2998
3506
|
onConfirmAbort,
|
|
@@ -3102,6 +3610,7 @@ function EmergencyRoot() {
|
|
|
3102
3610
|
active,
|
|
3103
3611
|
busy,
|
|
3104
3612
|
aborting: phase.kind === "aborting",
|
|
3613
|
+
simulating: phase.kind === "simulating",
|
|
3105
3614
|
loading: phase.kind === "initializing" || phase.kind === "activating",
|
|
3106
3615
|
locale: config.locale ?? "es",
|
|
3107
3616
|
responders,
|