import { useState, useEffect } from 'react' const KSUITE_PRICE = 6.58 const SITE_PRICE = 69 function getCosts(d) { const team = JSON.parse(d.team_members || '[]') const nbUsers = d.has_team ? team.length : 0 const doms = JSON.parse(d.domains_list || '[]') const ksuite = nbUsers * KSUITE_PRICE const site = d.has_website ? SITE_PRICE / 12 : 0 const extra = (d.extra_email_count || 0) * (d.extra_email_cost || 0) const domCost = doms.reduce((s, dm) => s + (dm.cost_per_year || 0) / 12, 0) return { nbUsers, doms, ksuite, site, extra, domCost, total: ksuite + site + extra + domCost } } export default function Dashboard({ token, user }) { const [domains, setDomains] = useState([]) const [activeTab, setActiveTab] = useState(-1) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [saved, setSaved] = useState(false) useEffect(() => { fetch('/api/domains', { headers: { Authorization: 'Bearer ' + token } }) .then(r => r.json()).then(d => { const all = d.parent ? [d.parent, ...d.children] : d.children setDomains(all) setLoading(false) }) }, [token]) const saveDomain = async (id) => { const d = domains.find(x => x.id === id) if (!d) return setSaving(true) await fetch('/api/domains/' + id, { method: 'PUT', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token }, body: JSON.stringify(d) }) setSaving(false) setSaved(true) setTimeout(() => setSaved(false), 2000) } const update = (id, field, val) => { setDomains(prev => prev.map(d => d.id === id ? { ...d, [field]: val } : d)) const t = setTimeout(() => saveDomain(id), 1200) return () => clearTimeout(t) } const addToList = (id, field) => { const d = domains.find(x => x.id === id) const list = JSON.parse(d[field] || '[]') list.push(field === 'team_members' ? { first_name: '', last_name: '' } : { name: '', cost_per_year: 0, expiry: '', hoster: '' }) update(id, field, JSON.stringify(list)) } const updateInList = (id, field, i, key, val) => { const d = domains.find(x => x.id === id) const list = JSON.parse(d[field] || '[]') list[i][key] = val update(id, field, JSON.stringify(list)) } const removeFromList = (id, field, i) => { const d = domains.find(x => x.id === id) const list = JSON.parse(d[field] || '[]') list.splice(i, 1) update(id, field, JSON.stringify(list)) } if (loading) return
Chargement...
if (domains.length === 0) return
Aucun domaine
const isAdmin = user.role === 'admin' // HOME VIEW const HomeView = () => { const allDomains = [] domains.forEach(d => { const doms = JSON.parse(d.domains_list || '[]') doms.forEach(dm => allDomains.push({ ...dm, society: d.company_name })) }) const grandTotal = domains.reduce((s, d) => s + getCosts(d).total, 0) const getColor = (expiry) => { if (!expiry) return 'text-slate-500' const diff = (new Date(expiry) - new Date()) / (1000 * 60 * 60 * 24) if (diff < 0) return 'text-red-400' if (diff < 30) return 'text-amber-400' if (diff < 90) return 'text-orange-300' return 'text-emerald-400' } const hosterBadge = (h) => { const m = { 'OVH': 'bg-sky-500/20 text-sky-300', 'Infomaniak': 'bg-emerald-500/20 text-emerald-300' } return m[h] || 'bg-slate-500/20 text-slate-300' } return (
{/* Recap par societe */}
{domains.map((d, i) => { const c = getCosts(d) return ( ) })}
{/* Grand total */} {isAdmin && (

Total general

{grandTotal.toFixed(2)}EUR/mois HT

)} {/* Tableau domaines */} {allDomains.length > 0 && (

Domaines

{allDomains.map((dm, i) => ( ))}
Societe Domaine Hebergeur Expiration
{dm.society} {dm.name || '—'} {dm.hoster || '—'} {dm.expiry || '—'}
)}
) } // DOMAIN FORM const DomainForm = ({ d }) => { const c = getCosts(d) const Txt = ({ value, onChange, placeholder }) => ( onChange(e.target.value)} placeholder={placeholder} className="w-full px-3 py-2 bg-slate-900/40 border border-slate-700/50 rounded-lg text-white text-sm placeholder-slate-600 focus:outline-none focus:ring-1 focus:ring-blue-500/50" /> ) const Num = ({ value, onChange, placeholder, step }) => ( onChange(parseFloat(e.target.value) || 0)} placeholder={placeholder} className="w-28 px-3 py-2 bg-slate-900/40 border border-slate-700/50 rounded-lg text-white text-sm text-right placeholder-slate-600 focus:outline-none focus:ring-1 focus:ring-blue-500/50 font-mono" /> ) const Toggle = ({ checked, onChange }) => ( ) const Select = ({ value, onChange, options }) => ( ) return (
{/* Utilisateurs */}
0 ? c.nbUsers + ' licence' + (c.nbUsers > 1 ? 's' : '') + ' Ksuite' : ''}>
Activer les utilisateurs update(d.id, 'has_team', v)} />
{d.has_team && (
{team.map((m, i) => (
updateInList(d.id, 'team_members', i, 'first_name', e.target.value)} placeholder="Prenom" className="flex-1 px-3 py-2 bg-slate-900/40 border border-slate-700/50 rounded-lg text-white text-sm placeholder-slate-600 focus:outline-none focus:ring-1 focus:ring-blue-500/50" /> updateInList(d.id, 'team_members', i, 'last_name', e.target.value)} placeholder="Nom" className="flex-1 px-3 py-2 bg-slate-900/40 border border-slate-700/50 rounded-lg text-white text-sm placeholder-slate-600 focus:outline-none focus:ring-1 focus:ring-blue-500/50" />
))}
)}
{/* Site internet */}
Site actif update(d.id, 'has_website', v)} />
{d.has_website && (
WordPress update(d.id, 'website_wordpress', v)} /> Gere par updateInList(d.id, 'domains_list', i, 'name', e.target.value)} placeholder="domaine.fr" className="flex-1 px-3 py-2 bg-slate-900/40 border border-slate-700/50 rounded-lg text-white text-sm placeholder-slate-600 focus:outline-none focus:ring-1 focus:ring-blue-500/50" /> updateInList(d.id, 'domains_list', i, 'cost_per_year', parseFloat(e.target.value) || 0)} placeholder="0" className="w-24 px-3 py-2 bg-slate-900/40 border border-slate-700/50 rounded-lg text-white text-sm text-right placeholder-slate-600 focus:outline-none focus:ring-1 focus:ring-blue-500/50 font-mono" /> updateInList(d.id, 'domains_list', i, 'expiry', e.target.value)} className="w-28 px-3 py-2 bg-slate-900/40 border border-slate-700/50 rounded-lg text-white text-sm focus:outline-none focus:ring-1 focus:ring-blue-500/50" /> updateInList(d.id, 'domains_list', i, 'hoster', e.target.value)} placeholder="Hebergeur" className="w-24 px-3 py-2 bg-slate-900/40 border border-slate-700/50 rounded-lg text-white text-sm placeholder-slate-600 focus:outline-none focus:ring-1 focus:ring-blue-500/50" />
))}
)}
{/* Notes */}