// Organizations, Users, Billing, Support, System, Audit, Settings, Org Detail function Table({ columns, rows, dark, onRowClick }) { const fgMuted = dark?'#9A9A9A':'#707070'; const border = dark?'#2B2B2B':'#F0F0F0'; return (
{columns.map(c=>( ))} {rows.map((r,i)=>( onRowClick && onRowClick(r)} style={{borderBottom:`1px solid ${border}`, cursor:onRowClick?'pointer':'default', transition:'background 80ms'}} onMouseEnter={e=>{ if(onRowClick) e.currentTarget.style.background=dark?'#222':'#FAFAFA'; }} onMouseLeave={e=>e.currentTarget.style.background='transparent'}> {columns.map(c=>( ))} ))}
{c.label}
{c.render ? c.render(r) : r[c.key]}
); } function FilterBar({ dark, filters, children }) { return (
{filters && filters.map((f,i)=>( ))} Več filtrov
{children}
); } // ═════ ORGANIZATIONS ═════ function OrganizationsPage({ dark, onOpenOrg, onNav }) { const planTone = {Enterprise:'purple', Pro:'blue', Starter:'neutral'}; const statusMap = {active:['success','Aktivna'], trial:['cyan','Poskusna'], past_due:['orange','Zapadlo'], suspended:['danger','Suspendirana']}; return ( <> s+o.rooms,0)} sob · ${ORGS.filter(o=>o.status==='active').length} aktivnih`} actions={<> Izvozi CSV Nova organizacija } /> (
{r.name.split(' ').slice(0,2).map(w=>w[0]).join('')}
{r.name}
{r.city} · {r.owner}
)}, {key:'plan', label:'Paket', render:r=>{r.plan}}, {key:'rooms', label:'Sobe', align:'right', render:r=>{r.rooms}}, {key:'properties', label:'Lokacije', align:'right', render:r=>{r.properties}}, {key:'occupancy', label:'Zasedenost', render:r=>(
80?'#2DD164':r.occupancy>50?'#2196F3':'#FF9800'}}/>
{r.occupancy}%
)}, {key:'mrr', label:'MRR', align:'right', render:r=>€{r.mrr}}, {key:'status', label:'Status', render:r=>{statusMap[r.status][1]}}, {key:'lastActive', label:'Zadnja aktivnost', render:r=>{r.lastActive}}, ]} rows={ORGS}/> ); } function OrgDetailPage({ org, dark, onBack }) { const fgMuted = dark?'#9A9A9A':'#707070'; const orgUsers = USERS.filter(u=>u.org===org.name); const orgTickets = TICKETS.filter(t=>t.org===org.name); const orgInvoices = INVOICES.filter(i=>i.org===org.name); return ( <> ← Nazaj Impersoniraj Suspendiraj } />
Uporabniki organizacije
{orgUsers.length===0 &&
Ni uporabnikov.
} {orgUsers.map((u,i)=>(
0?`1px solid ${dark?'#2B2B2B':'#F5F5F5'}`:'none'}}>
{u.name.split(' ').map(w=>w[0]).join('')}
{u.name}
{u.email} · {u.role}
{u.status}
))}
Odprte prijave
{orgTickets.length===0 &&
Nobene odprte prijave.
} {orgTickets.map((t,i)=>(
0?`1px solid ${dark?'#2B2B2B':'#F5F5F5'}`:'none'}}>
{t.id}
{t.priority}
{t.subject}
{t.opened} · {t.messages} sporočil
))}
Računi in naročnine
{r.id}}, {key:'amount', label:'Znesek', align:'right', render:r=>€{r.amount}}, {key:'due', label:'Rok plačila'}, {key:'paid', label:'Plačano', render:r=>r.paid || }, {key:'status', label:'Status', render:r=>{ const m = {paid:['success','Plačano'], past_due:['danger','Zapadlo'], trial:['cyan','Poskus'], void:['neutral','Stornirano']}; return {m[r.status][1]}; }}, ]} rows={orgInvoices}/> ); } // ═════ USERS ═════ function UsersPage({ dark }) { const statusMap = {active:['success','Aktiven'], invited:['cyan','Povabljen'], suspended:['danger','Suspendiran']}; const roleColor = { 'Superadmin':'purple','Hotel Admin':'blue','Front Desk Mgr':'cyan','Receptionist':'neutral','Housekeeping':'orange','Support Agent':'cyan' }; return ( <> u.twofa).length} z 2FA`} actions={<> Izvozi CSV Povabi uporabnika }/>
(
{r.name.split(' ').map(w=>w[0]).join('')}
{r.name}
{r.email}
)}, {key:'role', label:'Vloga', render:r=>{r.role}}, {key:'org', label:'Organizacija'}, {key:'twofa', label:'2FA', render:r=>r.twofa ? Omogočeno : Brez}, {key:'last', label:'Zadnja prijava', render:r=>{r.last}}, {key:'status', label:'Status', render:r=>{statusMap[r.status][1]}}, ]} rows={USERS}/> ); } // ═════ BILLING ═════ function BillingPage({ dark }) { const total = INVOICES.reduce((s,i)=>i.status==='paid'?s+i.amount:s,0); const pastDue = INVOICES.filter(i=>i.status==='past_due').reduce((s,i)=>s+i.amount,0); const statusMap = {paid:['success','Plačano'], past_due:['danger','Zapadlo'], trial:['cyan','Poskus'], void:['neutral','Stornirano']}; return ( <> Izvozi za računovodstvo Ročni račun }/>
s+o.mrr,0).toLocaleString('sl-SI')}`} delta="+5,6 %"/>
Razporeditev po paketih
{['Enterprise','Pro','Starter'].map(p=>{ const count = ORGS.filter(o=>o.plan===p).length; const mrr = ORGS.filter(o=>o.plan===p).reduce((s,o)=>s+o.mrr,0); const maxMrr = ORGS.reduce((s,o)=>s+o.mrr,0); const color = p==='Enterprise'?'#9C27B0':p==='Pro'?'#2196F3':'#9A9A9A'; return (
{p} {count} org. €{mrr.toLocaleString('sl-SI')}
); })}
Plačilne metode
{[['SEPA direktna bremenitev',8,'#2196F3'],['Kreditna kartica (Stripe)',3,'#00BCD4'],['Bančno nakazilo',1,'#FF9800']].map(([m,c,col])=>(
{m}
{c}
))}
Izdani računi — april 2026
1 zapadel
{r.id}}, {key:'org', label:'Organizacija'}, {key:'amount', label:'Znesek', align:'right', render:r=>€{r.amount}}, {key:'due', label:'Rok'}, {key:'paid', label:'Plačano', render:r=>r.paid || }, {key:'status', label:'Status', render:r=>{statusMap[r.status][1]}}, ]} rows={INVOICES}/> ); } // ═════ SUPPORT ═════ function SupportPage({ dark }) { const pMap = {urgent:['danger','Nujno'], high:['orange','Visoko'], med:['cyan','Srednje'], low:['neutral','Nizko']}; const sMap = {open:['orange','Odprto'], in_progress:['blue','V obdelavi'], waiting:['neutral','Čaka'], resolved:['success','Zaključeno']}; return ( <> t.status!=='resolved').length} odprtih prijav · povprečni čas odziva 42 min`} actions={<>Baza znanjaNova prijava}/>
t.status==='open').length}/> t.status==='in_progress').length}/> t.status==='waiting').length}/> t.status==='resolved').length}/>
{r.id}}, {key:'subject', label:'Zadeva', render:r=>
{r.subject}
{r.messages} sporočil
}, {key:'org', label:'Organizacija'}, {key:'priority', label:'Prioriteta', render:r=>{pMap[r.priority][1]}}, {key:'status', label:'Status', render:r=>{sMap[r.status][1]}}, {key:'agent', label:'Agent', render:r=>r.agent || nedodeljeno}, {key:'opened', label:'Odprto', render:r=>{r.opened}}, ]} rows={TICKETS}/> ); } // ═════ SYSTEM ═════ function SystemPage({ dark }) { const toneMap = {ok:'success', degraded:'orange', down:'danger'}; const lbl = {ok:'Deluje', degraded:'Upočasnjeno', down:'Nedosegljivo'}; return ( <> Poročilo uptimePonovno poveži vse}/>
Integracije
{INTEGRATIONS.map((it,i)=>(
0?`1px solid ${dark?'#2B2B2B':'#F5F5F5'}`:'none'}}>
{it.icon}
{it.name}
Zadnja sinhronizacija: {it.lastSync} · Latenca: {it.latency} ms
{lbl[it.status]}
{it.uptime}% uptime
Nastavi
))}
Podatkovni centri
{[['EU-Fra-1 (Frankfurt)','primary','ok',12],['EU-Hel-1 (Helsinki)','replica','ok',48],['Backup (Ljubljana)','backup','ok',null]].map(([n,role,st,lag])=>(
{n}
Online
{role}{lag!==null && ` · zamik ${lag} ms`}
))}
Podatki so shranjeni znotraj EU v skladu z GDPR in Zakonom o varstvu osebnih podatkov (ZVOP-2).
); } // ═════ AUDIT ═════ function AuditPage({ dark }) { const levelMap = {info:'blue', warn:'orange', error:'danger'}; return ( <> Izvozi dnevnikNapredno iskanje}/>
23.04 · {r.ts}}, {key:'level', label:'Stopnja', render:r=>{r.level.toUpperCase()}}, {key:'actor', label:'Akter', render:r=>{r.actor}}, {key:'action', label:'Dogodek', render:r=>{r.action}}, {key:'target', label:'Cilj', render:r=>{r.target}}, {key:'ip', label:'IP', render:r=>{r.ip}}, ]} rows={AUDIT}/> ); } // ═════ SETTINGS ═════ function SettingsPage({ dark }) { const [tab, setTab] = React.useState('plans'); const tabs = [['plans','Paketi in cene'],['features','Funkcionalnosti'],['branding','Blagovna znamka'],['regions','Regije'],['security','Varnost']]; const fgMuted = dark?'#9A9A9A':'#707070'; return ( <>
{tabs.map(([k,l])=>( ))}
{tab==='plans' && (
{[ {name:'Starter', price:149, rooms:'do 25', props:1, features:['Rezervacije in check-in','Osnovni kanali (Booking.com)','E-mail podpora','FURS davčna blagajna'], color:'#9A9A9A'}, {name:'Pro', price:389, rooms:'do 100', props:3, features:['Vse iz Starter','Vsi kanali + channel manager','Guest self-checkin','Poročila in analitika','24/7 telefonska podpora'], color:'#2196F3', featured:true}, {name:'Enterprise', price:1890, rooms:'neomejeno', props:'neomejeno', features:['Vse iz Pro','Kioski in POS','Multi-property',+'Custom integracije','Dedicated account manager','SLA 99,99 %'], color:'#9C27B0'}, ].map(p=>( {p.featured &&
Najbolj priljubljen
}
{p.name}
€{p.price}
/ mesec
{p.rooms} sob · {p.props} lokacij
{p.features.map(f=>(
{f}
))} Uredi paket
))}
)} {tab==='features' && ( {[ ['Channel Manager','Sinhronizacija Booking.com, Expedia, Airbnb',true], ['Samopostrežni kiosk','Check-in brez recepcije',true], ['Digitalni ključi','Mobile key preko Bluetooth',true], ['AI Pricing','Dinamično oblikovanje cen glede na povpraševanje',false], ['WhatsApp integracija','Komunikacija z gosti preko WhatsApp',true], ['Loyalty program','Točke zvestobe in popusti',false], ['FURS davčna blagajna','Obvezno za slovensko tržišče',true], ['AJPES poročanje','Avtomatsko poročanje gostov',true], ].map(([n,d,on],i)=>(
0?`1px solid ${dark?'#2B2B2B':'#F5F5F5'}`:'none'}}>
{n}
{d}
))}
)} {tab==='branding' &&
White-label konfiguracija — logotipi, barve, domene, e-mail predloge.
} {tab==='regions' &&
Aktivne regije: Slovenija (primarna), Hrvaška, Avstrija. Davčne stopnje in lokalizacija.
} {tab==='security' && (
Varnostne politike
{[['Obvezni 2FA za Superadmine',true],['Obvezni 2FA za Hotel Admine',true],['SSO (SAML 2.0)',false],['IP whitelist za admin konzolo',false],['Samodejna odjava po 30 min',true],['Zakasnitev po 5 neuspelih prijavah',true]].map(([l,on],i)=>(
0?`1px solid ${dark?'#2B2B2B':'#F5F5F5'}`:'none'}}>
{l}
))}
)} ); } function Toggle({ on, dark }) { const [v, setV] = React.useState(on); return ( ); } Object.assign(window, { OrganizationsPage, OrgDetailPage, UsersPage, BillingPage, SupportPage, SystemPage, AuditPage, SettingsPage });