diff --git a/frontend/src/components/Dashboard.jsx b/frontend/src/components/Dashboard.jsx index a48b501..947c23f 100644 --- a/frontend/src/components/Dashboard.jsx +++ b/frontend/src/components/Dashboard.jsx @@ -1,15 +1,14 @@ -import { useState, useEffect, useRef } from 'react' +import { useState, useEffect } from 'react' const KSUITE_PRICE = 6.58 const SITE_PRICE = 69 export default function Dashboard({ token, user }) { const [domains, setDomains] = useState([]) - const [activeTab, setActiveTab] = useState(0) + const [activeTab, setActiveTab] = useState(-1) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [saved, setSaved] = useState(false) - const saveTimer = useRef(null) useEffect(() => { fetch('/api/domains', { headers: { Authorization: 'Bearer ' + token } }) @@ -20,13 +19,6 @@ export default function Dashboard({ token, user }) { }) }, [token]) - const update = (id, field, val) => { - setDomains(prev => prev.map(d => d.id === id ? { ...d, [field]: val } : d)) - setSaved(false) - if (saveTimer.current) clearTimeout(saveTimer.current) - saveTimer.current = setTimeout(() => saveDomain(id), 1500) - } - const saveDomain = async (id) => { const d = domains.find(x => x.id === id) if (!d) return @@ -41,7 +33,11 @@ export default function Dashboard({ token, user }) { setTimeout(() => setSaved(false), 2000) } - const saveNow = () => { if (saveTimer.current) clearTimeout(saveTimer.current); saveDomain(domains[activeTab]?.id) } + const update = (id, field, val) => { + setDomains(prev => prev.map(d => d.id === id ? { ...d, [field]: val } : d)) + const timeout = setTimeout(() => saveDomain(id), 1200) + return () => clearTimeout(timeout) + } const addToList = (id, field) => { const d = domains.find(x => x.id === id) @@ -67,105 +63,113 @@ export default function Dashboard({ token, user }) { if (loading) return
Chargement...
if (domains.length === 0) return
Aucun domaine
- const d = domains[activeTab] - const team = JSON.parse(d.team_members || '[]') - const nbUsers = d.has_team ? team.length : 0 - const doms = JSON.parse(d.domains_list || '[]') const isAdmin = user.role === 'admin' - const ksuiteCost = nbUsers * KSUITE_PRICE - const siteCost = d.has_website ? SITE_PRICE / 12 : 0 - const extraCost = (d.extra_email_count || 0) * (d.extra_email_cost || 0) - const domsCost = doms.reduce((s, dm) => s + (dm.cost_per_year || 0) / 12, 0) - const total = ksuiteCost + siteCost + extraCost + domsCost + // 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 Row = ({ label, children }) => ( -
- {label} -
{children}
-
- ) + const getColor = (expiry) => { + if (!expiry) return '' + 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 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 }) => ( - - ) - - // Global summary for admin - const GlobalSummary = () => { - if (!isAdmin) return null - const all = domains.reduce((acc, dom) => { - const t = dom.has_team ? JSON.parse(dom.team_members || '[]').length : 0 - const dm = JSON.parse(dom.domains_list || '[]') - return { - users: acc.users + t, - sites: acc.sites + (dom.has_website ? 1 : 0), - doms: acc.doms + dm.length, - ksuite: acc.ksuite + t * KSUITE_PRICE, - site: acc.site + (dom.has_website ? SITE_PRICE / 12 : 0), - extra: acc.extra + (dom.extra_email_count || 0) * (dom.extra_email_cost || 0), - domCost: acc.domCost + dm.reduce((s, x) => s + (x.cost_per_year || 0) / 12, 0) - } - }, { users: 0, sites: 0, doms: 0, ksuite: 0, site: 0, extra: 0, domCost: 0 }) - const gTotal = all.ksuite + all.site + all.extra + all.domCost + const hosterColors = { + 'OVH': 'bg-sky-500/20 text-sky-300', + 'Infomaniak': 'bg-emerald-500/20 text-emerald-300', + 'Other': 'bg-slate-500/20 text-slate-300' + } return ( -
-

Resume global

-
-

{all.users}

Users

-

{all.ksuite.toFixed(0)}

Ksuite EUR/mois

-

{all.sites}

Sites

-

{all.doms}

Domaines

-

{gTotal.toFixed(2)}

Total EUR/mois HT

+
+

Tableau de bord

+
+ + + + + + + + + + + {allDomains.length === 0 && ( + + )} + {allDomains.map((dm, i) => ( + + + + + + + ))} + +
SocieteDomaineHebergeurExpiration
Aucun domaine declare
{dm.society}{dm.name || '—'} + + {dm.hoster || '—'} + + + {dm.expiry || '—'} +
) } - return ( -
- {/* Tabs */} -
- {domains.map((dom, i) => ( - - ))} -
+ // DOMAIN FORM VIEW + const DomainForm = ({ d }) => { + const team = JSON.parse(d.team_members || '[]') + const nbUsers = d.has_team ? team.length : 0 + const doms = JSON.parse(d.domains_list || '[]') - {/* Save indicator */} -
-

{d.company_name} {d.name}

-
- {saving && Sauvegarde...} - {saved && Sauvegarde} - {isAdmin && } -
-
+ const ksuiteCost = nbUsers * KSUITE_PRICE + const siteCost = d.has_website ? SITE_PRICE / 12 : 0 + const extraCost = (d.extra_email_count || 0) * (d.extra_email_cost || 0) + const domsCost = doms.reduce((s, dm) => s + (dm.cost_per_year || 0) / 12, 0) + const total = ksuiteCost + siteCost + extraCost + domsCost - {/* Form */} + const Row = ({ label, children }) => ( +
+ {label} +
{children}
+
+ ) + + 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, activeText, inactiveText, color }) => ( + + ) + + const Select = ({ value, onChange, options }) => ( + + ) + + return (
{/* Utilisateurs */}
@@ -311,8 +315,42 @@ export default function Dashboard({ token, user }) { )}
+ ) + } - + return ( +
+ {/* Tabs */} +
+ + {domains.map((dom, i) => ( + + ))} +
+ + {/* Save indicator */} +
+ {activeTab === -1 ? ( +

Tableau de bord

+ ) : ( +

{domains[activeTab].company_name}

+ )} +
+ {saving && Sauvegarde...} + {saved && Sauvegarde} +
+
+ + {/* Content */} + {activeTab === -1 ? : }
) }