// Shared UI primitives for Argo
const { useState, useEffect, useRef, useMemo } = React;
// Rileva il sistema PayTourist il cui comune corrisponde all'indirizzo della struttura.
// Controlla tutte le parti separate da virgola nell'indirizzo (case-insensitive).
function _detectPtSystem(address, systems) {
if (!address || !systems.length) return null;
const parts = address.split(",").map(p => p.trim().toLowerCase()).filter(Boolean);
return systems.find(s => {
const comune = s.comune.toLowerCase();
return parts.some(p => p === comune || p.startsWith(comune + " ") || comune.startsWith(p));
}) || null;
}
// --------- ICONS ---------
const I = {
dashboard: (p) => ,
home: (p) => ,
card: (p) => ,
settings: (p) => ,
logout: (p) => ,
plus: (p) => ,
qr: (p) => ,
edit: (p) => ,
download: (p) => ,
check: (p) => ,
x: (p) => ,
upload: (p) => ,
file: (p) => ,
trash: (p) => ,
back: (p) => ,
chat: (p) => ,
users: (p) => ,
building: (p) => ,
sparkle: (p) => ,
door: (p) => ,
pin: (p) => ,
bell: (p) => ,
search: (p) => ,
whatsapp: (p) => ,
};
// --------- LOGO ---------
function Logo({ size = "md" }) {
const h = size === "lg" ? 176 : size === "sm" ? 112 : 144;
return (
);
}
// --------- BADGE ---------
function StatusBadge({ status }) {
const label = window.STATUS_IT[status] || status;
return {label};
}
// --------- SIDEBAR ---------
function Sidebar({ current, onNavigate, onLogout, host, open }) {
const items = [
{ id: "dashboard", label: "Dashboard", icon: I.dashboard },
{ id: "properties", label: "Le mie strutture", icon: I.home },
{ id: "subscription", label: "Abbonamento", icon: I.card },
{ id: "settings", label: "Impostazioni", icon: I.settings },
];
// Iniziali avatar: prime lettere del nome
const initials = (host?.full_name || "?")
.split(" ").map(p => p[0]).slice(0, 2).join("").toUpperCase();
return (
);
}
// --------- TOPBAR ---------
function TopBar({ title, breadcrumb, right, onMenuClick }) {
return (
{/* Hamburger — visibile ≤1024px tramite CSS */}
{breadcrumb ? (
{breadcrumb.map((b, i) => (
{i > 0 && /}
{b.label}
))}
) : (
{title}
)}
{right}
);
}
// --------- TOAST ---------
function useToast() {
const [msg, setMsg] = useState(null);
useEffect(() => {
if (!msg) return;
const t = setTimeout(() => setMsg(null), 2400);
return () => clearTimeout(t);
}, [msg]);
const show = (text, type = "success") => setMsg({ text, type });
const node = msg ? (
{msg.type === "success" && }
{msg.text}
) : null;
return [show, node];
}
// --------- MODAL ---------
function Modal({ open, onClose, children }) {
if (!open) return null;
return (
e.stopPropagation()}>
{children}
);
}
// --------- QR CODE (SVG, static pattern) ---------
function QRCode({ size = 160, label }) {
// Deterministic pseudo-QR pattern (visual only). 25x25 grid.
const seed = (label || "argo").split("").reduce((a, c) => a + c.charCodeAt(0), 0);
const grid = 25;
const cell = size / grid;
const rnd = (i) => {
let x = (seed * 9301 + i * 49297) % 233280;
return (x / 233280);
};
const cells = [];
for (let y = 0; y < grid; y++) {
for (let x = 0; x < grid; x++) {
// skip finder pattern squares
const inFinder = (x < 8 && y < 8) || (x > 16 && y < 8) || (x < 8 && y > 16);
if (inFinder) continue;
if (rnd(x * grid + y + 1) > 0.52) {
cells.push();
}
}
}
const finder = (fx, fy) => (
);
return (
);
}
// --------- BADGE (generico con varianti) ---------
// Affianca StatusBadge (legacy) — usa variant invece di status
function Badge({ variant = "plain", children, style }) {
return (
{children}
);
}
// --------- TOGGLE ---------
// Toggle on/off accessibile con transizione cubic-bezier
// Props: checked, onChange, ariaLabelOn, ariaLabelOff, size ("md" | "sm")
function Toggle({ checked, onChange, ariaLabelOn, ariaLabelOff, size = "md" }) {
// ariaLabel dinamico: quando off mostra l'azione di abilitazione, viceversa
const label = checked
? (ariaLabelOff || "Disabilita")
: (ariaLabelOn || "Abilita");
const trackClass = [
"toggle-track",
checked ? "on" : "",
size === "sm" ? "toggle-track-sm" : "",
].filter(Boolean).join(" ");
return (
);
}
// --------- PT BANNER (3 stati PayTourist) ---------
// state: "uncovered" | "matched-checkin-off" | "matched-no-token"
// comune: nome del comune coperto (per stati 2 e 3)
// onSettingsClick: callback per aprire impostazioni account (stato 3)
function PtBanner({ state, comune, onSettingsClick }) {
if (state === "uncovered") {
return (
·
Il comune di questa struttura non è ancora coperto da PayTourist.
);
}
if (state === "matched-checkin-off") {
return (
i
{comune} è coperto da PayTourist. Abilita il check-in digitale qui sopra per attivare l'invio automatico delle schedine.
);
}
if (state === "matched-no-token") {
return (
!
{comune} è coperto da PayTourist.
{" "}Aggiungi il tuo token nelle{" "}
{" "}
per attivare l'integrazione.
);
}
return null;
}
// --------- PT STRUCTURE PICKER ---------
// Card composta per la configurazione della struttura PayTourist
// Props: comune, ptEnabled, structures, loading, value, onChange,
// onSave, onValidate, onDisable, saving, validating
function PtStructurePicker({ comune, ptEnabled, structures, loading, value, onChange, onSave, onValidate, onDisable, saving, validating, error }) {
// Se la struttura selezionata (salvata in DB) non compare nell'elenco recuperato
// dall'API PayTourist (es. API momentaneamente irraggiungibile, o l'account non
// la ritorna più), mostriamo comunque un'opzione sintetica per l'id salvato:
// altrimenti il