Blog
bash
cat > /home/claude/dosar-horeca.html << 'HTMLEOF'
<!DOCTYPE html>
<html lang="ro">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dosar HoReCa — Control Documente</title>
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500;600&family=DM+Mono:wght@400;500&display=swap');
:root {
--bg: #0F0F0F;
--bg2: #181818;
--bg3: #212121;
--bg4: #2A2A2A;
--border: #2E2E2E;
--border2: #3A3A3A;
--text: #F0EEE8;
--text2: #9A9890;
--text3: #6A6860;
--accent: #E8C547;
--accent2: #C9A830;
--red: #E24B4A;
--red-bg: #2A1818;
--orange: #EF9F27;
--orange-bg: #2A2010;
--green: #5CB85C;
--green-bg: #152015;
--blue: #4A9EE8;
--blue-bg: #101E2A;
--radius: 10px;
--radius-sm: 6px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'DM Sans', sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
font-size: 14px;
}
/* LAYOUT */
.layout { display: flex; min-height: 100vh; }
.sidebar {
width: 220px;
background: var(--bg2);
border-right: 0.5px solid var(--border);
display: flex;
flex-direction: column;
flex-shrink: 0;
position: fixed;
top: 0; left: 0; bottom: 0;
z-index: 10;
}
.sidebar-logo {
padding: 20px 18px 16px;
border-bottom: 0.5px solid var(--border);
}
.sidebar-logo-title {
font-size: 15px;
font-weight: 600;
color: var(--accent);
letter-spacing: -0.02em;
}
.sidebar-logo-sub {
font-size: 11px;
color: var(--text3);
margin-top: 2px;
font-family: 'DM Mono', monospace;
}
.sidebar-nav { padding: 10px 10px; flex: 1; }
.nav-item {
display: flex;
align-items: center;
gap: 10px;
padding: 9px 10px;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 13px;
color: var(--text2);
margin-bottom: 2px;
transition: all 0.15s;
border: none;
background: none;
width: 100%;
text-align: left;
font-family: 'DM Sans', sans-serif;
}
.nav-item:hover { background: var(--bg3); color: var(--text); }
.nav-item.active { background: var(--bg3); color: var(--text); font-weight: 500; }
.nav-item.active .nav-icon { color: var(--accent); }
.nav-icon { font-size: 15px; flex-shrink: 0; }
.nav-badge {
margin-left: auto;
background: var(--red-bg);
color: var(--red);
font-size: 10px;
font-weight: 600;
padding: 1px 6px;
border-radius: 10px;
font-family: 'DM Mono', monospace;
}
.nav-badge.warn { background: var(--orange-bg); color: var(--orange); }
.sidebar-footer {
padding: 12px 14px;
border-top: 0.5px solid var(--border);
font-size: 11px;
color: var(--text3);
font-family: 'DM Mono', monospace;
}
.main {
margin-left: 220px;
flex: 1;
min-height: 100vh;
}
.topbar {
background: var(--bg2);
border-bottom: 0.5px solid var(--border);
padding: 14px 24px;
display: flex;
align-items: center;
justify-content: space-between;
position: sticky;
top: 0;
z-index: 5;
}
.topbar-title { font-size: 16px; font-weight: 500; }
.topbar-sub { font-size: 12px; color: var(--text3); margin-top: 1px; }
.content { padding: 20px 24px; }
/* ALERTS */
.alert {
border-radius: var(--radius-sm);
padding: 10px 14px;
font-size: 12px;
margin-bottom: 8px;
border: 0.5px solid;
line-height: 1.5;
}
.al-red { background: var(--red-bg); color: #F08080; border-color: #5A2020; }
.al-orange { background: var(--orange-bg); color: #F0B060; border-color: #5A4010; }
.al-green { background: var(--green-bg); color: #80C880; border-color: #1A4A1A; }
/* STATS */
.stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
margin-bottom: 20px;
}
.stat {
background: var(--bg2);
border: 0.5px solid var(--border);
border-radius: var(--radius);
padding: 14px 16px;
}
.stat-n { font-size: 28px; font-weight: 600; line-height: 1; font-family: 'DM Mono', monospace; }
.stat-l { font-size: 11px; color: var(--text3); margin-top: 5px; text-transform: uppercase; letter-spacing: 0.06em; }
.stat-exp .stat-n { color: var(--red); }
.stat-sn .stat-n { color: var(--orange); }
.stat-ok .stat-n { color: var(--green); }
.stat-acc .stat-n { color: var(--accent); }
/* BUTTONS */
.btn {
padding: 7px 14px;
border-radius: var(--radius-sm);
font-size: 12px;
cursor: pointer;
border: 0.5px solid var(--border2);
background: var(--bg3);
color: var(--text2);
font-family: 'DM Sans', sans-serif;
transition: all 0.15s;
display: inline-flex;
align-items: center;
gap: 6px;
}
.btn:hover { background: var(--bg4); color: var(--text); }
.btn-primary { background: var(--accent); color: #0F0F0F; border-color: var(--accent); font-weight: 600; }
.btn-primary:hover { background: var(--accent2); border-color: var(--accent2); }
.btn-danger { background: var(--red-bg); color: var(--red); border-color: #5A2020; }
.btn-info { background: var(--blue-bg); color: var(--blue); border-color: #1A3A5A; }
.btn-sm { padding: 4px 9px; font-size: 11px; }
/* TOOLBAR */
.toolbar {
display: flex;
gap: 8px;
margin-bottom: 14px;
flex-wrap: wrap;
align-items: center;
}
.toolbar input, .toolbar select {
padding: 7px 11px;
border: 0.5px solid var(--border2);
border-radius: var(--radius-sm);
font-size: 12px;
background: var(--bg2);
color: var(--text);
font-family: 'DM Sans', sans-serif;
}
.toolbar input { flex: 1; min-width: 160px; }
.toolbar input::placeholder { color: var(--text3); }
/* SUBTABS */
.subtabs {
display: flex;
gap: 4px;
flex-wrap: wrap;
margin-bottom: 14px;
}
.stab {
padding: 5px 12px;
border: 0.5px solid var(--border);
border-radius: var(--radius-sm);
font-size: 12px;
cursor: pointer;
background: var(--bg2);
color: var(--text2);
display: flex;
align-items: center;
gap: 6px;
transition: all 0.12s;
font-family: 'DM Sans', sans-serif;
}
.stab.active { border-color: var(--accent); color: var(--accent); background: rgba(232,197,71,0.08); }
.stab:hover { color: var(--text); border-color: var(--border2); }
/* CARDS */
.card {
background: var(--bg2);
border: 0.5px solid var(--border);
border-radius: var(--radius);
margin-bottom: 8px;
overflow: hidden;
transition: border-color 0.15s;
}
.card:hover { border-color: var(--border2); }
.card.st-exp { border-left: 2px solid var(--red); }
.card.st-sn { border-left: 2px solid var(--orange); }
.card.st-ok { border-left: 2px solid var(--green); }
.card.st-nd { border-left: 2px solid var(--border2); }
.card.st-lip { border-left: 2px solid var(--orange); }
.card-head {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 14px;
}
.card-head.clickable { cursor: pointer; }
.avatar {
width: 36px; height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 600;
flex-shrink: 0;
font-family: 'DM Mono', monospace;
}
.card-info { flex: 1; min-width: 0; }
.card-name { font-size: 13px; font-weight: 500; margin-bottom: 3px; }
.card-sub { font-size: 11px; color: var(--text2); }
.card-badges { display: flex; gap: 4px; flex-wrap: wrap; margin-top: 5px; }
.card-actions { display: flex; gap: 5px; flex-shrink: 0; }
/* BADGES */
.badge {
display: inline-block;
padding: 2px 7px;
border-radius: 4px;
font-size: 10px;
font-weight: 500;
font-family: 'DM Mono', monospace;
}
.b-exp { background: var(--red-bg); color: #F08080; }
.b-sn { background: var(--orange-bg); color: #F0B060; }
.b-ok { background: var(--green-bg); color: #80C880; }
.b-nd { background: var(--bg3); color: var(--text3); }
.b-info { background: var(--blue-bg); color: var(--blue); }
.b-acc { background: rgba(232,197,71,0.12); color: var(--accent); }
/* INST TAGS */
.inst-tags { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 5px; }
.itag { font-size: 10px; padding: 2px 7px; border-radius: 4px; font-weight: 500; font-family: 'DM Mono', monospace; }
/* CARD BODY */
.card-body { padding: 0 14px 12px; border-top: 0.5px solid var(--border); }
.sec-lbl {
font-size: 10px;
font-weight: 500;
color: var(--text3);
letter-spacing: 0.08em;
text-transform: uppercase;
padding: 8px 0 4px;
}
.doc-row {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 0;
border-bottom: 0.5px solid var(--border);
font-size: 12px;
}
.doc-row:last-child { border-bottom: none; }
.doc-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
.doc-name { flex: 1; min-width: 0; }
.doc-date { font-size: 11px; color: var(--text3); flex-shrink: 0; font-family: 'DM Mono', monospace; }
.missing-row {
display: flex;
align-items: center;
gap: 6px;
padding: 5px 0;
font-size: 11px;
color: #F08080;
border-bottom: 0.5px solid var(--border);
}
.missing-row:last-child { border-bottom: none; }
.notes-t { font-size: 11px; color: var(--text3); font-style: italic; margin-top: 3px; }
/* EXPAND ARROW */
.exp-arrow { font-size: 10px; color: var(--text3); transition: transform 0.2s; flex-shrink: 0; }
.expanded .exp-arrow { transform: rotate(180deg); }
/* CAT HEADER */
.cat-hdr {
font-size: 10px;
font-weight: 600;
color: var(--text3);
letter-spacing: 0.1em;
text-transform: uppercase;
margin: 16px 0 8px;
padding-bottom: 6px;
border-bottom: 0.5px solid var(--border);
font-family: 'DM Mono', monospace;
}
/* CONTROL CARD */
.ctrl-card {
background: var(--bg2);
border: 0.5px solid var(--border);
border-radius: var(--radius);
margin-bottom: 10px;
overflow: hidden;
}
.ctrl-head {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 14px;
border-bottom: 0.5px solid var(--border);
}
.ctrl-name { font-size: 14px; font-weight: 500; }
.ctrl-desc { font-size: 11px; color: var(--text3); margin-top: 1px; }
.ctrl-body { padding: 8px 14px; }
.ctrl-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 5px 0;
border-bottom: 0.5px solid var(--border);
gap: 8px;
font-size: 12px;
}
.ctrl-row:last-child { border-bottom: none; }
.ctrl-miss {
display: flex;
align-items: center;
gap: 6px;
padding: 5px 0;
font-size: 11px;
color: #F08080;
border-bottom: 0.5px solid var(--border);
}
.ctrl-miss:last-child { border-bottom: none; }
/* EMPTY */
.empty {
text-align: center;
padding: 3rem;
color: var(--text3);
font-size: 13px;
}
/* LOADING */
.loading {
display: flex;
align-items: center;
justify-content: center;
padding: 3rem;
color: var(--text3);
gap: 10px;
font-size: 13px;
}
.spinner {
width: 18px; height: 18px;
border: 2px solid var(--border2);
border-top-color: var(--accent);
border-radius: 50%;
animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* MODAL */
.modal-bg {
position: fixed; inset: 0;
background: rgba(0,0,0,0.7);
display: flex; align-items: center; justify-content: center;
z-index: 100;
backdrop-filter: blur(3px);
}
.modal {
background: var(--bg2);
border: 0.5px solid var(--border2);
border-radius: var(--radius);
padding: 20px;
width: 480px;
max-width: 96vw;
max-height: 88vh;
overflow-y: auto;
}
.modal h2 { font-size: 15px; font-weight: 500; margin-bottom: 16px; color: var(--text); }
.fg { margin-bottom: 12px; }
.fg label { display: block; font-size: 11px; color: var(--text3); margin-bottom: 4px; text-transform: uppercase; letter-spacing: 0.06em; font-family: 'DM Mono', monospace; }
.fg input, .fg select, .fg textarea {
width: 100%;
padding: 8px 11px;
border: 0.5px solid var(--border2);
border-radius: var(--radius-sm);
font-size: 12px;
background: var(--bg3);
color: var(--text);
font-family: 'DM Sans', sans-serif;
}
.fg input:focus, .fg select:focus, .fg textarea:focus {
outline: none;
border-color: var(--accent);
}
.fg textarea { min-height: 60px; resize: vertical; }
.fr2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.mf {
display: flex; justify-content: flex-end; gap: 8px;
margin-top: 16px; padding-top: 16px;
border-top: 0.5px solid var(--border);
}
/* INST CHECKBOXES */
.inst-cbs { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px; }
.icb {
display: flex; align-items: center; gap: 5px;
padding: 4px 10px;
border: 0.5px solid var(--border);
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 11px;
transition: all 0.12s;
font-family: 'DM Mono', monospace;
color: var(--text2);
}
.icb:hover { border-color: var(--border2); }
.icb.on { font-weight: 500; }
/* COLOR STRIP */
.color-strip { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 4px; }
.color-opt {
width: 24px; height: 24px;
border-radius: 50%;
cursor: pointer;
border: 2px solid transparent;
transition: all 0.12s;
}
.color-opt.selected { border-color: var(--text); transform: scale(1.15); }
/* SCROLLBAR */
::-webkit-scrollbar { width: 5px; }
::-webkit-scrollbar-track { background: var(--bg2); }
::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 3px; }
select option { background: var(--bg3); }
</style>
</head>
<body>
<div class="layout">
<!-- SIDEBAR -->
<div class="sidebar">
<div class="sidebar-logo">
<div class="sidebar-logo-title">Dosar HoReCa</div>
<div class="sidebar-logo-sub">v1.0 · Supabase</div>
</div>
<div class="sidebar-nav">
<button class="nav-item active" onclick="goPage('dashboard')" id="nav-dashboard">
<span class="nav-icon">◈</span> Rezumat
</button>
<button class="nav-item" onclick="goPage('docs')" id="nav-docs">
<span class="nav-icon">◉</span> Documente firmă
</button>
<button class="nav-item" onclick="goPage('angajati')" id="nav-angajati">
<span class="nav-icon">◎</span> Angajați
</button>
<button class="nav-item" onclick="goPage('controale')" id="nav-controale">
<span class="nav-icon">◫</span> Pe instituții
</button>
</div>
<div class="sidebar-footer" id="sidebar-footer">Se încarcă...</div>
</div>
<!-- MAIN -->
<div class="main">
<div class="topbar">
<div>
<div class="topbar-title" id="topbar-title">Rezumat</div>
<div class="topbar-sub" id="topbar-sub">DSP · ANPC · ITM · ISU · DSVSA · Primărie · Poliție</div>
</div>
<div id="topbar-actions"></div>
</div>
<div class="content">
<div id="alerts-box" style="margin-bottom:14px"></div>
<div id="stats-box"></div>
<div id="page-content">
<div class="loading"><div class="spinner"></div> Se conectează la baza de date...</div>
</div>
</div>
</div>
</div>
<div id="modal" style="display:none"></div>
<script>
// ─── SUPABASE CONFIG ───────────────────────────────────────────────
const SUPA_URL = 'https://rzsyqgvodwkrkaappole.supabase.co';
const SUPA_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InJ6c3lxZ3ZvZHdrcmthYXBwb2xlIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Nzc0Njk0MjQsImV4cCI6MjA5MzA0NTQyNH0.dPAd16RG47E8IF0oJkylvHzQ9SbLi-SHIjR2IHc9VP8';
const { createClient } = supabase;
const db = createClient(SUPA_URL, SUPA_KEY);
// ─── CONSTANTE ────────────────────────────────────────────────────
const INST = [
{id:'DSP', label:'DSP', color:'#E24B4A', bg:'rgba(226,75,74,0.15)', desc:'Sănătate Publică'},
{id:'ANPC', label:'ANPC', color:'#EF9F27', bg:'rgba(239,159,39,0.15)', desc:'Protecția Consumatorului'},
{id:'ITM', label:'ITM', color:'#4A9EE8', bg:'rgba(74,158,232,0.15)', desc:'Inspecția Muncii'},
{id:'ISU', label:'ISU', color:'#9F77DD', bg:'rgba(159,119,221,0.15)', desc:'Situații Urgență'},
{id:'DSVSA', label:'DSVSA', color:'#3DC495', bg:'rgba(61,196,149,0.15)', desc:'Sanitar-Veterinar'},
{id:'PRM', label:'Primărie', color:'#A8A098', bg:'rgba(168,160,152,0.15)', desc:'Primărie'},
{id:'POL', label:'Poliție', color:'#4A9EE8', bg:'rgba(74,158,232,0.12)', desc:'Poliție Locală'},
];
const CTRL_OBL = {
DSP: ['Autorizație sanitară de funcționare','Contract DDD','Fișe aptitudine angajați','Analize medicale personal','Plan igienizare + grafic curățenie','Proceduri HACCP','Dovadă apă potabilă','Contract colectare deșeuri'],
ANPC: ['Autorizație de funcționare (primărie)','Certificat înregistrare firmă (CUI)','Afișare prețuri (meniu/bar)','Gramaje și informații produse','Politică reclamații + registru reclamații','Bon fiscal (casă de marcat)'],
ITM: ['Contracte individuale de muncă (CIM)','Revisal','Fișe de post','Pontaj','Dosar personal angajat','Medicină muncii — contract + fișe','SSM — instruiri + fișe semnate'],
ISU: ['Autorizație ISU / Scenariu securitate incendiu','Plan evacuare','Contract mentenanță stingătoare','Verificare instalații electrice','Instruiri PSI angajați'],
DSVSA: ['Autorizație sanitar-veterinară','Contract DDD','Facturi proveniență marfă','HACCP complet','Fișe temperaturi depozitare','Trasabilitate produse'],
PRM: ['Autorizație de funcționare','Contract salubritate','Contract utilități (apă, curent)'],
POL: ['Autorizație funcționare','Contract muzică UCMR-ADA','Casă de marcat fiscalizată'],
};
const DOC_CATS = [
{id:'autorizatii',label:'Autorizații & Avize'},
{id:'haccp',label:'HACCP & Igienă'},
{id:'contracte',label:'Contracte'},
{id:'securitate',label:'Securitate & ISU'},
{id:'fiscal',label:'Fiscal & Registre'},
{id:'altele',label:'Altele'},
];
const DOC_TYPES_BY_CAT = {
autorizatii:['Autorizație sanitară de funcționare','Autorizație sanitar-veterinară (DSVSA)','Autorizație ISU / Scenariu securitate incendiu','Autorizație de funcționare (primărie)','Certificat înregistrare firmă (CUI)','Licență OANC','Autorizație vânzare alcool'],
haccp:['Proceduri HACCP','Plan igienizare + grafic curățenie','Dovadă apă potabilă (buletin analiză)','Registru temperaturi frigidere','Registru curățenie','Fișe temperaturi depozitare','Trasabilitate produse'],
contracte:['Contract DDD (dezinsecție/deratizare)','Contract colectare deșeuri / salubritate','Contract muzică UCMR-ADA','Contract utilități (apă)','Contract utilități (curent)','Contract mentenanță stingătoare','Contract chirie','Contract pază / alarmare'],
securitate:['Plan evacuare','Verificare instalații electrice','Stingătoare — buletin verificare','Marcaje evacuare','Iluminat de siguranță'],
fiscal:['Casă de marcat fiscalizată','Revisal','Pontaj','Registru reclamații','Politică reclamații'],
altele:['Afișare prețuri (meniu/bar)','Gramaje și informații produse','Acord vecini','Aviz urbanism / mediu','Altul'],
};
const EMP_DOC_TYPES = [
{id:'cim', label:'Contract individual de muncă (CIM)', inst:['ITM'], obl:true},
{id:'med', label:'Fișă aptitudine — medicină muncii', inst:['DSP','ITM'], obl:true},
{id:'analize',label:'Analize medicale (coprocultură)', inst:['DSP'], obl:true},
{id:'ssm', label:'Fișă instruire SSM', inst:['ITM'], obl:true},
{id:'psi', label:'Fișă instruire PSI', inst:['ISU'], obl:true},
{id:'post', label:'Fișă de post (semnată)', inst:['ITM'], obl:true},
{id:'gdpr', label:'Acord GDPR', inst:['ITM'], obl:false},
{id:'ri', label:'Regulament intern (semnat)', inst:['ITM'], obl:false},
];
const POSTURI = ['Barman','Ospătar','Ajutor bucătar','Bucătar','Casier','Manager tură','Îngrijitor','Paznic','Altul'];
const TURE = ['Dimineață','Seară','Noapte','Split','Flexibil'];
const COLORS = [
{id:'blue', bg:'#1A3A5A', fg:'#4A9EE8'},
{id:'teal', bg:'#103A28', fg:'#3DC495'},
{id:'coral', bg:'#3A1A10', fg:'#E87050'},
{id:'purple',bg:'#2A1A4A', fg:'#9F77DD'},
{id:'amber', bg:'#3A2A08', fg:'#E8C547'},
{id:'pink', bg:'#3A1030', fg:'#E870A8'},
{id:'green', bg:'#1A3A10', fg:'#70C840'},
{id:'gray', bg:'#2A2A2A', fg:'#A8A098'},
];
// ─── STATE ────────────────────────────────────────────────────────
let docs = [];
let emps = [];
let empDocs = {};
let page = 'dashboard';
let subCtrl = 'all';
let subDocTab = 'all';
let subEmpTab = 'activi';
let expanded = {};
let _selColor = 'blue';
let _cbInst = [];
let _editDid = null;
let _editEid = null;
let _addDocEid= null;
let _editEdid = null;
// ─── HELPERS ──────────────────────────────────────────────────────
function days(s){ if(!s)return null; const d=new Date(s),n=new Date(); n.setHours(0,0,0,0); return Math.round((d-n)/86400000); }
function dst(s){ if(!s)return 'nd'; const d=days(s); return d<0?'exp':d<=30?'sn':'ok'; }
function fdt(s){ if(!s)return '—'; const[y,m,d]=s.split('-'); return `${d}.${m}.${y}`; }
function ini(n){ return n.split(' ').map(w=>w[0]).join('').substring(0,2).toUpperCase(); }
function colOf(id){ return COLORS.find(c=>c.id===id)||COLORS[0]; }
function iInf(id){ return INST.find(i=>i.id===id)||{label:id,color:'#888',bg:'rgba(128,128,128,0.15)'}; }
function badge(s,d){
if(s==='nd') return '<span class="badge b-nd">Fără exp.</span>';
if(s==='exp') return `<span class="badge b-exp">Expirat ${Math.abs(d)}z</span>`;
if(s==='sn') return `<span class="badge b-sn">${d}z</span>`;
return `<span class="badge b-ok">${d}z</span>`;
}
function docSt(doc){ return dst(doc.expiry); }
function empSt(emp){
const edocs = empDocs[emp.id]||[];
const ss = edocs.map(d=>dst(d.expiry));
if(ss.includes('exp')) return 'exp';
if(ss.includes('sn')) return 'sn';
const obl = EMP_DOC_TYPES.filter(t=>t.obl).map(t=>t.id);
if(obl.some(t=>!edocs.find(d=>d.type===t))) return 'lip';
return 'ok';
}
function missingEmp(emp){
const edocs = empDocs[emp.id]||[];
return EMP_DOC_TYPES.filter(t=>t.obl && !edocs.find(d=>d.type===t.id));
}
function loading(){ return '<div class="loading"><div class="spinner"></div> Se încarcă...</div>'; }
// ─── SUPABASE CRUD ────────────────────────────────────────────────
async function loadAll(){
const [{ data: d }, { data: e }, { data: ed }] = await Promise.all([
db.from('documente').select('*').order('created_at'),
db.from('angajati').select('*').order('created_at'),
db.from('angajat_documente').select('*').order('created_at'),
]);
docs = d||[];
emps = e||[];
empDocs = {};
(ed||[]).forEach(x=>{ if(!empDocs[x.angajat_id]) empDocs[x.angajat_id]=[]; empDocs[x.angajat_id].push(x); });
updateSidebar();
}
async function addDoc(obj){
const {data,error} = await db.from('documente').insert([obj]).select();
if(error) return alert('Eroare: '+error.message);
docs.push(data[0]); renderAll();
}
async function updDoc(id,obj){
const {error} = await db.from('documente').update(obj).eq('id',id);
if(error) return alert('Eroare: '+error.message);
docs = docs.map(d=>d.id===id?{...d,...obj}:d); renderAll();
}
async function delDocDB(id){
const {error} = await db.from('documente').delete().eq('id',id);
if(error) return alert('Eroare: '+error.message);
docs = docs.filter(d=>d.id!==id); renderAll();
}
async function addEmp(obj){
const {data,error} = await db.from('angajati').insert([obj]).select();
if(error) return alert('Eroare: '+error.message);
emps.push(data[0]); empDocs[data[0].id]=[]; expanded['e'+data[0].id]=true; renderAll();
}
async function updEmp(id,obj){
const {error} = await db.from('angajati').update(obj).eq('id',id);
if(error) return alert('Eroare: '+error.message);
emps = emps.map(e=>e.id===id?{...e,...obj}:e); renderAll();
}
async function delEmpDB(id){
const {error} = await db.from('angajati').delete().eq('id',id);
if(error) return alert('Eroare: '+error.message);
emps = emps.filter(e=>e.id!==id); delete empDocs[id]; renderAll();
}
async function addEmpDoc(obj){
const {data,error} = await db.from('angajat_documente').insert([obj]).select();
if(error) return alert('Eroare: '+error.message);
if(!empDocs[obj.angajat_id]) empDocs[obj.angajat_id]=[];
empDocs[obj.angajat_id].push(data[0]); renderAll();
}
async function updEmpDoc(id,obj,eid){
const {error} = await db.from('angajat_documente').update(obj).eq('id',id);
if(error) return alert('Eroare: '+error.message);
empDocs[eid] = (empDocs[eid]||[]).map(d=>d.id===id?{...d,...obj}:d); renderAll();
}
async function delEmpDocDB(id,eid){
const {error} = await db.from('angajat_documente').delete().eq('id',id);
if(error) return alert('Eroare: '+error.message);
empDocs[eid] = (empDocs[eid]||[]).filter(d=>d.id!==id); renderAll();
}
// ─── SIDEBAR UPDATE ───────────────────────────────────────────────
function updateSidebar(){
const expD = docs.filter(d=>docSt(d)==='exp').length;
const snD = docs.filter(d=>docSt(d)==='sn').length;
const expE = emps.filter(e=>e.status==='activ'&&empSt(e)==='exp').length;
const snE = emps.filter(e=>e.status==='activ'&&(empSt(e)==='sn'||empSt(e)==='lip')).length;
const prob = expD+snD+expE+snE;
document.getElementById('sidebar-footer').innerHTML =
`${emps.filter(e=>e.status==='activ').length} angajați · ${docs.length} doc.<br>` +
(prob ? `<span style="color:var(--orange)">${prob} necesită atenție</span>` : '<span style="color:var(--green)">Totul ok</span>');
// update nav badges
const dBadge = document.querySelector('#nav-docs .nav-badge');
if(dBadge) dBadge.remove();
if(expD+snD>0){
const b=document.createElement('span');
b.className='nav-badge'+(expD>0?'':' warn');
b.textContent=expD+snD;
document.getElementById('nav-docs').appendChild(b);
}
const eBadge = document.querySelector('#nav-angajati .nav-badge');
if(eBadge) eBadge.remove();
if(expE+snE>0){
const b=document.createElement('span');
b.className='nav-badge'+(expE>0?'':' warn');
b.textContent=expE+snE;
document.getElementById('nav-angajati').appendChild(b);
}
}
// ─── NAV ──────────────────────────────────────────────────────────
function goPage(p){
page = p;
['dashboard','docs','angajati','controale'].forEach(x=>{
document.getElementById('nav-'+x).className='nav-item'+(x===p?' active':'');
});
const titles = {dashboard:'Rezumat',docs:'Documente firmă',angajati:'Angajați',controale:'Pe instituții de control'};
document.getElementById('topbar-title').textContent = titles[p];
const addBtns = {
docs:'<button class="btn btn-primary" onclick="openAddDoc()">+ Document</button>',
angajati:'<button class="btn btn-primary" onclick="openAddEmp()">+ Angajat</button>',
dashboard:'', controale:'',
};
document.getElementById('topbar-actions').innerHTML = addBtns[p]||'';
renderAll();
}
// ─── ALERTS + STATS ───────────────────────────────────────────────
function renderAlerts(){
const expD = docs.filter(d=>docSt(d)==='exp');
const snD = docs.filter(d=>docSt(d)==='sn');
const expE = emps.filter(e=>e.status==='activ'&&empSt(e)==='exp');
const snE = emps.filter(e=>e.status==='activ'&&empSt(e)==='sn');
const lipE = emps.filter(e=>e.status==='activ'&&empSt(e)==='lip');
let h='';
if(expD.length||expE.length) h+=`<div class="alert al-red"><strong>Expirate:</strong> ${[...expD.map(d=>d.name),...expE.map(e=>e.nume)].join(' · ')}</div>`;
if(snD.length||snE.length||lipE.length) h+=`<div class="alert al-orange">${snD.length?`<strong>Doc. expiră curând:</strong> ${snD.map(d=>d.name).join(', ')}<br>`:''}${snE.length?`<strong>Angajați — expiră curând:</strong> ${snE.map(e=>e.nume).join(', ')}<br>`:''}${lipE.length?`<strong>Doc. lipsă la:</strong> ${lipE.map(e=>e.nume).join(', ')}`:''}</div>`;
if(!h) h='<div class="alert al-green">Toate documentele și dosarele angajaților sunt în regulă.</div>';
document.getElementById('alerts-box').innerHTML = h;
}
function renderStats(){
let h='';
if(page==='docs'){
const t=docs.length,e=docs.filter(d=>docSt(d)==='exp').length,s=docs.filter(d=>docSt(d)==='sn').length,o=docs.filter(d=>docSt(d)==='ok').length;
h=`<div class="stats"><div class="stat stat-acc"><div class="stat-n">${t}</div><div class="stat-l">Total doc.</div></div><div class="stat stat-exp"><div class="stat-n">${e}</div><div class="stat-l">Expirate</div></div><div class="stat stat-sn"><div class="stat-n">${s}</div><div class="stat-l">Expiră curând</div></div><div class="stat stat-ok"><div class="stat-n">${o}</div><div class="stat-l">În regulă</div></div></div>`;
} else if(page==='angajati'){
const ac=emps.filter(e=>e.status==='activ');
const t=ac.length,e=ac.filter(x=>empSt(x)==='exp').length,s=ac.filter(x=>empSt(x)==='sn'||empSt(x)==='lip').length,o=ac.filter(x=>empSt(x)==='ok').length;
h=`<div class="stats"><div class="stat stat-acc"><div class="stat-n">${t}</div><div class="stat-l">Angajați activi</div></div><div class="stat stat-exp"><div class="stat-n">${e}</div><div class="stat-l">Doc. expirate</div></div><div class="stat stat-sn"><div class="stat-n">${s}</div><div class="stat-l">Atenție</div></div><div class="stat stat-ok"><div class="stat-n">${o}</div><div class="stat-l">Complet ok</div></div></div>`;
} else if(page==='dashboard'){
const td=docs.length,te=emps.filter(e=>e.status==='activ').length;
const prob=docs.filter(d=>docSt(d)==='exp'||docSt(d)==='sn').length+emps.filter(e=>e.status==='activ'&&(empSt(e)==='exp'||empSt(e)==='sn'||empSt(e)==='lip')).length;
h=`<div class="stats"><div class="stat stat-acc"><div class="stat-n">${td}</div><div class="stat-l">Doc. firmă</div></div><div class="stat stat-acc"><div class="stat-n">${te}</div><div class="stat-l">Angajați activi</div></div><div class="stat stat-exp"><div class="stat-n">${prob}</div><div class="stat-l">Necesită atenție</div></div><div class="stat stat-ok"><div class="stat-n">${td+te-prob}</div><div class="stat-l">În regulă</div></div></div>`;
}
document.getElementById('stats-box').innerHTML = h;
}
// ─── PAGE RENDERS ─────────────────────────────────────────────────
function renderAll(){ renderAlerts(); renderStats(); updateSidebar();
if(page==='dashboard') renderDashboard();
else if(page==='docs') renderDocs();
else if(page==='angajati') renderAngajati();
else if(page==='controale') renderControale();
}
function renderDashboard(){
const expD=docs.filter(d=>docSt(d)==='exp'), snD=docs.filter(d=>docSt(d)==='sn');
const expE=emps.filter(e=>e.status==='activ'&&empSt(e)==='exp'), snE=emps.filter(e=>e.status==='activ'&&(empSt(e)==='sn'||empSt(e)==='lip'));
let h='';
if(expD.length||snD.length){
h+=`<div class="cat-hdr">Documente firmă — acțiune necesară</div>`;
[...expD,...snD].forEach(doc=>{
const s=docSt(doc),d=days(doc.expiry);
const itags=(doc.inst||[]).map(i=>{const inf=iInf(i);return `<span class="itag" style="background:${inf.bg};color:${inf.color}">${inf.label}</span>`;}).join('');
h+=`<div class="card st-${s}"><div class="card-head"><div class="card-info"><div class="card-name">${doc.name}</div><div class="card-sub">${doc.type||''}</div><div class="inst-tags">${itags}</div><div class="card-badges" style="margin-top:5px">${badge(s,d)}</div></div><div class="card-actions"><button class="btn btn-sm" onclick="goPage('docs')">Vezi</button></div></div></div>`;
});
}
if(expE.length||snE.length){
h+=`<div class="cat-hdr">Angajați — acțiune necesară</div>`;
[...expE,...snE].forEach(emp=>{
const es=empSt(emp),c=colOf(emp.color);
h+=`<div class="card st-${es}"><div class="card-head"><div class="avatar" style="background:${c.bg};color:${c.fg}">${ini(emp.nume)}</div><div class="card-info"><div class="card-name">${emp.nume}</div><div class="card-sub">${emp.post} · ${emp.tura}</div><div class="card-badges"><span class="badge ${es==='exp'?'b-exp':'b-sn'}">${es==='exp'?'Doc. expirate':es==='lip'?'Lipsă doc.':'Expiră curând'}</span></div></div><div class="card-actions"><button class="btn btn-sm" onclick="goPage('angajati')">Dosar</button></div></div></div>`;
});
}
if(!expD.length&&!snD.length&&!expE.length&&!snE.length) h='<div class="empty">◈<br><br>Totul în regulă.</div>';
document.getElementById('page-content').innerHTML = h;
}
function renderDocs(){
const insts=[{id:'all',label:'Toate'},...INST];
let h=`<div class="subtabs">${insts.map(i=>`<button class="stab ${subDocTab===i.id?'active':''}" onclick="setSubDoc('${i.id}')">${i.id!=='all'?`<span style="width:8px;height:8px;border-radius:50%;background:${i.color};display:inline-block"></span>`:''}${i.label||i.id}</button>`).join('')}</div>`;
h+=`<div class="toolbar"><input type="text" id="docQ" placeholder="Caută document..." oninput="filterDocs()"><select id="docSf" onchange="filterDocs()"><option value="">Toate statusurile</option><option value="exp">Expirate</option><option value="sn">Expiră curând</option><option value="ok">Ok</option></select></div><div id="doc-items"></div>`;
document.getElementById('page-content').innerHTML = h;
filterDocs();
}
function setSubDoc(id){ subDocTab=id; renderDocs(); }
function filterDocs(){
const q=(document.getElementById('docQ')||{value:''}).value.toLowerCase();
const sf=(document.getElementById('docSf')||{value:''}).value;
let filtered=docs.filter(d=>{
if(subDocTab!=='all'&&!(d.inst||[]).includes(subDocTab)) return false;
if(sf&&docSt(d)!==sf) return false;
if(q&&!((d.name+d.type+(d.inst||[]).join('')+(d.notes||'')).toLowerCase().includes(q))) return false;
return true;
});
filtered.sort((a,b)=>{const sv={exp:0,sn:1,ok:2,nd:3};return sv[docSt(a)]-sv[docSt(b)];});
if(!filtered.length){document.getElementById('doc-items').innerHTML='<div class="empty">Niciun document.</div>';return;}
const byCat={};
filtered.forEach(d=>{if(!byCat[d.cat])byCat[d.cat]=[];byCat[d.cat].push(d);});
let dh='';
DOC_CATS.forEach(({id,label})=>{
if(!byCat[id]) return;
dh+=`<div class="cat-hdr">${label}</div>`;
byCat[id].forEach(doc=>{
const s=docSt(doc),d=days(doc.expiry);
const itags=(doc.inst||[]).map(ii=>{const inf=iInf(ii);return `<span class="itag" style="background:${inf.bg};color:${inf.color}">${inf.label}</span>`;}).join('');
dh+=`<div class="card st-${s}"><div class="card-head">
<div class="card-info">
<div class="card-name">${doc.name}</div>
<div class="card-sub">${doc.type||''}${doc.issued_by?' · '+doc.issued_by:''}${doc.expiry?' · exp: '+fdt(doc.expiry):''}</div>
<div class="inst-tags">${itags}</div>
${doc.notes?`<div class="notes-t">${doc.notes}</div>`:''}
<div class="card-badges" style="margin-top:5px">${badge(s,d)}</div>
</div>
<div class="card-actions">
<button class="btn btn-sm" onclick="openEditDoc(${doc.id})">Edit</button>
<button class="btn btn-sm btn-danger" onclick="confirmDelDoc(${doc.id})">×</button>
</div>
</div></div>`;
});
});
document.getElementById('doc-items').innerHTML = dh;
}
function renderAngajati(){
const tabs=[{id:'activi',l:'Activi'},{id:'inactivi',l:'Ieșiți'},{id:'toate',l:'Toți'}];
let h=`<div class="subtabs">${tabs.map(t=>`<button class="stab ${subEmpTab===t.id?'active':''}" onclick="setSubEmp('${t.id}')">${t.l}</button>`).join('')}</div>`;
h+=`<div class="toolbar"><input type="text" id="empQ" placeholder="Caută angajat..." oninput="filterEmps()"></div><div id="emp-items"></div>`;
document.getElementById('page-content').innerHTML = h;
filterEmps();
}
function setSubEmp(t){ subEmpTab=t; renderAngajati(); }
function filterEmps(){
const q=(document.getElementById('empQ')||{value:''}).value.toLowerCase();
let filtered=emps.filter(e=>{
if(subEmpTab==='activi'&&e.status!=='activ') return false;
if(subEmpTab==='inactivi'&&e.status==='activ') return false;
if(q&&!((e.nume+e.post+e.tura).toLowerCase().includes(q))) return false;
return true;
});
if(!filtered.length){document.getElementById('emp-items').innerHTML='<div class="empty">Niciun angajat.</div>';return;}
let eh='';
filtered.forEach(emp=>{
const es=empSt(emp),c=colOf(emp.color),miss=missingEmp(emp),isExp=expanded['e'+emp.id];
const edocs=empDocs[emp.id]||[];
const stLbl={exp:'Doc. expirate',sn:'Expiră curând',ok:'Complet ok',lip:`${miss.length} doc. lipsă`,nd:'—'}[es];
const stCl={exp:'b-exp',sn:'b-sn',ok:'b-ok',lip:'b-sn',nd:'b-nd'}[es];
eh+=`<div class="card st-${es} ${isExp?'expanded':''}">
<div class="card-head clickable" onclick="toggleEmp(${emp.id})">
<div class="avatar" style="background:${c.bg};color:${c.fg}">${ini(emp.nume)}</div>
<div class="card-info">
<div class="card-name">${emp.nume}</div>
<div class="card-sub">${emp.post} · ${emp.tura}${emp.angajat?' · din '+fdt(emp.angajat):''}</div>
<div class="card-badges"><span class="badge ${stCl}">${stLbl}</span><span class="badge b-nd">${edocs.length} doc.</span></div>
</div>
<div class="card-actions" onclick="event.stopPropagation()">
<button class="btn btn-sm btn-info" onclick="openAddEmpDoc(${emp.id})">+ Doc</button>
<button class="btn btn-sm" onclick="openEditEmp(${emp.id})">Edit</button>
<button class="btn btn-sm btn-danger" onclick="confirmDelEmp(${emp.id})">×</button>
</div>
<span class="exp-arrow">▼</span>
</div>`;
if(isExp){
eh+=`<div class="card-body">`;
if(edocs.length){
eh+=`<div class="sec-lbl">Documente</div>`;
edocs.forEach(doc=>{
const s=dst(doc.expiry),d=days(doc.expiry);
const dotC={exp:'var(--red)',sn:'var(--orange)',ok:'var(--green)',nd:'var(--text3)'}[s];
eh+=`<div class="doc-row"><span class="doc-dot" style="background:${dotC}"></span><span class="doc-name">${doc.label||doc.type}${doc.note?` <span style="color:var(--text3);font-style:italic">— ${doc.note}</span>`:''}</span><span class="doc-date">${doc.expiry?fdt(doc.expiry):'—'}</span>${badge(s,d)}<button class="btn btn-sm" onclick="openEditEmpDoc(${emp.id},${doc.id})" style="font-size:10px">Edit</button><button class="btn btn-sm btn-danger" onclick="confirmDelEmpDoc(${emp.id},${doc.id})" style="font-size:10px">×</button></div>`;
});
}
if(miss.length){
eh+=`<div class="sec-lbl" style="color:var(--red)">Lipsă obligatorii</div>`;
miss.forEach(dt=>{eh+=`<div class="missing-row">⚠ ${dt.label}<button class="btn btn-sm btn-info" style="margin-left:auto" onclick="openAddEmpDoc(${emp.id},'${dt.id}')">Adaugă</button></div>`;});
}
eh+=`</div>`;
}
eh+=`</div>`;
});
document.getElementById('emp-items').innerHTML = eh;
}
function toggleEmp(id){ expanded['e'+id]=!expanded['e'+id]; filterEmps(); }
function renderControale(){
const insts2=[{id:'all',label:'Toate',color:'var(--text3)'},...INST];
let h=`<div class="subtabs">${insts2.map(i=>`<button class="stab ${subCtrl===i.id?'active':''}" onclick="setSubCtrl('${i.id}')">${i.id!=='all'?`<span style="width:8px;height:8px;border-radius:50%;background:${i.color};display:inline-block"></span>`:''}${i.label}</button>`).join('')}</div><div id="ctrl-items"></div>`;
document.getElementById('page-content').innerHTML = h;
const show = subCtrl==='all'?INST:INST.filter(i=>i.id===subCtrl);
let ch='';
show.forEach(inst=>{
const obl=CTRL_OBL[inst.id]||[];
let rows='',miss=[];
obl.forEach(docName=>{
const found=docs.find(d=>(d.inst||[]).includes(inst.id)&&(d.type===docName||d.name.toLowerCase().includes(docName.toLowerCase().substring(0,14))));
if(found){const s=docSt(found),d=days(found.expiry);rows+=`<div class="ctrl-row"><span style="flex:1">${docName}</span><div style="display:flex;gap:5px;align-items:center">${badge(s,d)}<span class="badge b-info">Obligatoriu</span></div></div>`;}
else miss.push(docName);
});
const typeMap={DSP:['med','analize'],ITM:['cim','med','ssm','post'],ISU:['psi']};
const needed=typeMap[inst.id]||[];
if(needed.length){
const ac=emps.filter(e=>e.status==='activ');
let er='';
ac.forEach(emp=>{
needed.forEach(t=>{
const edt=EMP_DOC_TYPES.find(x=>x.id===t);
const fd=(empDocs[emp.id]||[]).find(d=>d.type===t);
if(fd){const s=dst(fd.expiry),d=days(fd.expiry);er+=`<div class="ctrl-row"><span style="flex:1">${emp.nume} — ${edt.label}</span>${badge(s,d)}</div>`;}
else if(edt.obl) er+=`<div class="ctrl-miss">⚠ ${emp.nume} — ${edt.label} lipsă</div>`;
});
});
if(er) rows+=`<div class="sec-lbl">Documente angajați</div>`+er;
}
const misH=miss.map(m=>`<div class="ctrl-miss">⚠ Lipsă: ${m}</div>`).join('');
ch+=`<div class="ctrl-card">
<div class="ctrl-head">
<span style="width:12px;height:12px;border-radius:50%;background:${inst.color};display:inline-block;flex-shrink:0"></span>
<div style="flex:1"><div class="ctrl-name">${inst.label}</div><div class="ctrl-desc">${inst.desc}</div></div>
<div style="font-size:11px;font-weight:500;color:${miss.length?'var(--red)':'var(--green)'};">${miss.length?miss.length+' doc. lipsă':'Complet'}</div>
</div>
<div class="ctrl-body">${rows}${misH}${!rows&&!misH?'<div style="font-size:12px;color:var(--text3);padding:6px 0">Niciun document înregistrat.</div>':''}</div>
</div>`;
});
document.getElementById('ctrl-items').innerHTML = ch;
}
function setSubCtrl(id){ subCtrl=id; renderControale(); }
// ─── MODALS DOCS ──────────────────────────────────────────────────
function openAddDoc(){ _editDid=null; _cbInst=[]; showDocModal(); }
function openEditDoc(id){
_editDid=id;
const doc=docs.find(d=>d.id===id);
_cbInst=[...(doc.inst||[])];
showDocModal(doc);
}
function showDocModal(doc){
const d=doc||{};
const catOpts=DOC_CATS.map(c=>`<option value="${c.id}" ${c.id===(d.cat||'')?'selected':''}>${c.label}</option>`).join('');
const typeOpts=Object.entries(DOC_TYPES_BY_CAT).map(([cat,types])=>`<optgroup label="${DOC_CATS.find(c=>c.id===cat)?.label||cat}">${types.map(t=>`<option value="${t}" ${t===(d.type||'')?'selected':''}>${t}</option>`).join('')}</optgroup>`).join('');
const icbs=INST.map(i=>{const on=_cbInst.includes(i.id);return `<div class="icb ${on?'on':''}" id="icb-${i.id}" style="${on?`background:${i.bg};border-color:${i.color};color:${i.color}`:''}" onclick="toggleInst('${i.id}','${i.color}','${i.bg.replace(/'/g,'"')}')"><span style="width:7px;height:7px;border-radius:50%;background:${i.color};display:inline-block"></span>${i.label}</div>`;}).join('');
showM(`<h2>${_editDid?'Editează document':'Document nou'}</h2>
<div class="fr2"><div class="fg"><label>Categorie</label><select id="f-cat">${catOpts}</select></div><div class="fg"><label>Tip</label><select id="f-type">${typeOpts}</select></div></div>
<div class="fg"><label>Denumire</label><input id="f-name" value="${d.name||''}" placeholder="ex: Autorizație sanitară DSP 2025"></div>
<div class="fr2"><div class="fg"><label>Data emiterii</label><input type="date" id="f-issued" value="${d.issued||''}"></div><div class="fg"><label>Data expirării</label><input type="date" id="f-expiry" value="${d.expiry||''}"></div></div>
<div class="fg"><label>Emis de</label><input id="f-issby" value="${d.issued_by||''}" placeholder="DSP, ISU, ANAF..."></div>
<div class="fg"><label>Cerut la control de</label><div class="inst-cbs">${icbs}</div></div>
<div class="fg"><label>Note</label><textarea id="f-notes">${d.notes||''}</textarea></div>
<div class="mf"><button class="btn" onclick="closeModal()">Anulează</button><button class="btn btn-primary" onclick="saveDocF()">Salvează</button></div>`);
}
function toggleInst(id,color,bg){
const el=document.getElementById('icb-'+id);
if(_cbInst.includes(id)){_cbInst=_cbInst.filter(x=>x!==id);el.className='icb';el.removeAttribute('style');}
else{_cbInst.push(id);el.className='icb on';el.style=`background:${bg};border-color:${color};color:${color}`;}
}
async function saveDocF(){
const name=document.getElementById('f-name').value.trim();
if(!name){alert('Completați denumirea.');return;}
const obj={name,cat:document.getElementById('f-cat').value,type:document.getElementById('f-type').value,issued:document.getElementById('f-issued').value||null,expiry:document.getElementById('f-expiry').value||null,issued_by:document.getElementById('f-issby').value.trim()||null,inst:_cbInst,notes:document.getElementById('f-notes').value.trim()||null};
closeModal();
if(_editDid) await updDoc(_editDid,obj);
else await addDoc(obj);
}
async function confirmDelDoc(id){
const doc=docs.find(d=>d.id===id);
if(!confirm(`Ștergi "${doc?.name}"?`)) return;
await delDocDB(id);
}
// ─── MODALS ANGAJAȚI ──────────────────────────────────────────────
function openAddEmp(){ _editEid=null; _selColor='blue'; showEmpModal(); }
function openEditEmp(id){ _editEid=id; const e=emps.find(x=>x.id===id); _selColor=e.color; showEmpModal(e); }
function showEmpModal(e){
const d=e||{};
const cps=COLORS.map(c=>`<div class="color-opt ${c.id===(d.color||'blue')?'selected':''}" id="cp-${c.id}" style="background:${c.fg};border-color:${c.id===(d.color||'blue')?'var(--text)':'transparent'}" onclick="pickCol('${c.id}')"></div>`).join('');
showM(`<h2>${_editEid?'Editează angajat':'Angajat nou'}</h2>
<div class="fr2"><div class="fg"><label>Nume complet</label><input id="f-enume" value="${d.nume||''}" placeholder="Ionescu Maria"></div><div class="fg"><label>Post</label><select id="f-epost">${POSTURI.map(p=>`<option ${p===(d.post||'')?'selected':''}>${p}</option>`).join('')}</select></div></div>
<div class="fr2"><div class="fg"><label>Tură</label><select id="f-etura">${TURE.map(t=>`<option ${t===(d.tura||'')?'selected':''}>${t}</option>`).join('')}</select></div><div class="fg"><label>Status</label><select id="f-estat"><option value="activ" ${(d.status||'activ')==='activ'?'selected':''}>Activ</option><option value="inactiv" ${d.status==='inactiv'?'selected':''}>Ieșit</option></select></div></div>
<div class="fr2"><div class="fg"><label>Data angajării</label><input type="date" id="f-eang" value="${d.angajat||''}"></div><div class="fg"><label>Telefon</label><input id="f-etel" value="${d.tel||''}" placeholder="07xx..."></div></div>
<div class="fg"><label>Culoare identificare</label><div class="color-strip">${cps}</div></div>
<div class="fg"><label>Note</label><textarea id="f-enote">${d.note||''}</textarea></div>
<div class="mf"><button class="btn" onclick="closeModal()">Anulează</button><button class="btn btn-primary" onclick="saveEmpF()">Salvează</button></div>`);
}
function pickCol(id){
_selColor=id;
COLORS.forEach(c=>{const el=document.getElementById('cp-'+c.id);if(!el)return;el.className='color-opt'+(c.id===id?' selected':'');el.style.borderColor=c.id===id?'var(--text)':'transparent';});
}
async function saveEmpF(){
const nume=document.getElementById('f-enume').value.trim();
if(!nume){alert('Completați numele.');return;}
const obj={nume,post:document.getElementById('f-epost').value,tura:document.getElementById('f-etura').value,status:document.getElementById('f-estat').value,angajat:document.getElementById('f-eang').value||null,tel:document.getElementById('f-etel').value.trim()||null,color:_selColor,note:document.getElementById('f-enote').value.trim()||null};
closeModal();
if(_editEid) await updEmp(_editEid,obj);
else await addEmp(obj);
}
async function confirmDelEmp(id){
const e=emps.find(x=>x.id===id);
if(!confirm(`Ștergi angajatul "${e?.nume}" și toate documentele sale?`)) return;
await delEmpDB(id);
}
// ─── MODALS DOC ANGAJAT ───────────────────────────────────────────
function openAddEmpDoc(eid,prefillType){
_addDocEid=eid; _editEdid=null; showEmpDocModal(null,prefillType);
}
function openEditEmpDoc(eid,did){
_addDocEid=eid; _editEdid=did;
const doc=(empDocs[eid]||[]).find(d=>d.id===did);
showEmpDocModal(doc);
}
function showEmpDocModal(doc,prefillType){
const d=doc||{};
const typeOpts=EMP_DOC_TYPES.map(t=>`<option value="${t.id}" ${(t.id===(d.type||prefillType||''))?'selected':''}>${t.label}${t.obl?' ✓':''}</option>`).join('');
showM(`<h2>${_editEdid?'Editează document':'Document angajat'}</h2>
<div class="fg"><label>Tip document</label><select id="f-etype">${typeOpts}</select></div>
<div class="fg"><label>Denumire (opțional)</label><input id="f-elabel" value="${d.label||''}" placeholder="Lasă gol pentru titlu automat"></div>
<div class="fr2"><div class="fg"><label>Data emiterii</label><input type="date" id="f-eissued" value="${d.issued||''}"></div><div class="fg"><label>Data expirării</label><input type="date" id="f-eexpiry" value="${d.expiry||''}"></div></div>
<div class="fg"><label>Note</label><textarea id="f-enote2">${d.note||''}</textarea></div>
<div class="mf"><button class="btn" onclick="closeModal()">Anulează</button><button class="btn btn-primary" onclick="saveEmpDocF()">Salvează</button></div>`);
}
async function saveEmpDocF(){
const tid=document.getElementById('f-etype').value;
const ti=EMP_DOC_TYPES.find(t=>t.id===tid);
const label=document.getElementById('f-elabel').value.trim()||ti.label;
const obj={angajat_id:_addDocEid,type:tid,label,issued:document.getElementById('f-eissued').value||null,expiry:document.getElementById('f-eexpiry').value||null,note:document.getElementById('f-enote2').value.trim()||null};
closeModal();
if(_editEdid) await updEmpDoc(_editEdid,obj,_addDocEid);
else await addEmpDoc(obj);
expanded['e'+_addDocEid]=true;
}
async function confirmDelEmpDoc(eid,did){
const doc=(empDocs[eid]||[]).find(d=>d.id===did);
if(!confirm(`Ștergi "${doc?.label||doc?.type}"?`)) return;
await delEmpDocDB(did,eid);
}
// ─── MODAL UTILS ──────────────────────────────────────────────────
function showM(html){ document.getElementById('modal').style.display='flex'; document.getElementById('modal').innerHTML=`<div class="modal-bg" onclick="if(event.target===this)closeModal()"><div class="modal">${html}</div></div>`; }
function closeModal(){ document.getElementById('modal').style.display='none'; }
// ─── INIT ─────────────────────────────────────────────────────────
(async()=>{
await loadAll();
goPage('dashboard');
})();
</script>
</body>
</html>
HTMLEOF
echo "Done"
Output
Done
Sorry, but nothing was found. Please try a search with different keywords.