// Florio Analytics — Feedback tab (ported from js/pages/feedback.js).
// Lists in-app feedback from the `feedback` collection, split into Unread / Read.
// Each card can be toggled read/unread (writes `read` back to Firestore).
// Rebuilt in the new design kit. All queries / interactions preserved.
(function () {
const NS = window.HyperChartsDesignSystem_e51a95;
const I = window.HyperIcons;
const { useState, useEffect, useCallback } = React;
function fmt(n) { return (window.FlorioAPI && window.FlorioAPI.formatNumber) ? window.FlorioAPI.formatNumber(n) : String(n ?? 0); }
function formatDate(item) {
if (item.createdAt && typeof item.createdAt.toDate === 'function') {
return item.createdAt.toDate().toLocaleDateString('en-GB', {
day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit',
});
}
return 'Unknown date';
}
function FeedbackCard({ item, isRead, onToggle, busy }) {
return (
{item.email || 'No email'}
{formatDate(item)}
{item.message}
);
}
function Feedback({ range }) {
const P = window.PA;
const [items, setItems] = useState(null);
const [busyId, setBusyId] = useState(null);
const load = useCallback(async () => {
const { db, collection, query, orderBy, getDocs } = window.FlorioDB;
// Data source: `feedback` collection, ordered by createdAt desc.
const q = query(collection(db, 'feedback'), orderBy('createdAt', 'desc'));
const snap = await getDocs(q);
const out = [];
snap.forEach((d) => out.push(Object.assign({ id: d.id }, d.data())));
return out;
}, []);
useEffect(() => {
let alive = true;
(async () => {
await window.FlorioReady;
try {
const out = await load();
if (alive) setItems(out);
} catch (e) {
console.error('[Feedback] load failed:', e);
if (alive) setItems([]);
}
})();
return () => { alive = false; };
}, [load]);
// Toggle read state → write to Firestore, then update local list.
const toggleRead = useCallback(async (id, markAsRead) => {
const { db, doc, updateDoc } = window.FlorioDB;
setBusyId(id);
try {
await updateDoc(doc(db, 'feedback', id), { read: markAsRead });
setItems((cur) => (cur || []).map((it) => (it.id === id ? Object.assign({}, it, { read: markAsRead }) : it)));
} catch (e) {
console.error('[Feedback] toggle failed:', e);
alert('Update failed: ' + e.message);
} finally {
setBusyId(null);
}
}, []);
// Loading spinner while data null.
if (!items) {
return ;
}
const unread = items.filter((it) => !it.read);
const read = items.filter((it) => it.read);
const Empty = ({ note }) => (
{I.heart({ size: 24 })}
{note}
);
return (
{/* KPI row */}
{unread.length === 0 ? : (
{unread.map((it) => )}
)}
{read.length === 0 ? : (
{read.map((it) => )}
)}
);
}
(window.FlorioTabs = window.FlorioTabs || {}).feedback = Feedback;
})();