// hrm.jsx — module NHÂN SỰ & CHẤM CÔNG (HRM) + widget dashboard.
// Dữ liệu: p.hrm (main.jsx hoist, seed hdata.jsx, DB 0047 khi đăng nhập) + mutators generic
// p.hrmAdd/hrmPatch/hrmRemove(key,…). Trang chi tiết hồ sơ: hrm-profile.jsx (nạp trước file này).
const HRM_APP = APPS.find(a => a.id === 'people');

/* ---------- helpers cục bộ ---------- */
function hrmTimeHM(iso){ if (!iso) return '—'; const d = new Date(iso); return String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0'); }
function hrmIsoFrom(date, hm){ return hm ? new Date(date + 'T' + hm + ':00').toISOString() : null; }
// Đếm ngày làm việc T2–T6 trong [start, end], trừ ngày lễ
function hrmWorkingDays(start, end, holidays){
  if (!start || !end || end < start) return 0;
  const hs = new Set((holidays || []).map(h => h.date));
  let n = 0; const d = new Date(start + 'T00:00:00'), e = new Date(end + 'T00:00:00');
  while (d <= e) { const dow = d.getDay(); const iso = d.toLocaleDateString('en-CA'); if (dow !== 0 && dow !== 6 && !hs.has(iso)) n++; d.setDate(d.getDate() + 1); }
  return n;
}
function hrmMonthDates(ym){
  const days = new Date(+ym.slice(0, 4), +ym.slice(5), 0).getDate();
  return Array.from({ length:days }, (_, i) => ym + '-' + String(i + 1).padStart(2, '0'));
}
// Hook dùng chung: settings + hồ sơ của tôi + quyền quản lý (UI — DB mới là ranh giới)
function useHrm(){
  const p = usePortal();
  const st = hrmSettings(p.appSettings);
  const myEmp = hrmMyEmployee(p.hrm.employees, p.me);
  const canManage = p.role === 'admin' || p.role === 'manager';
  return { p, st, myEmp, canManage, data:p.hrm };
}
function OtMeter({ label, used, cap, level }){
  const pct = Math.min(100, Math.round(used / cap * 100));
  const color = level === 'over' ? 'var(--error)' : level === 'warn' ? 'var(--warning)' : 'var(--success)';
  return (
    <div className="col g4" style={{ flex:1, minWidth:150 }}>
      <div className="flex justify-between" style={{ fontSize:'var(--fs-sm)' }}>
        <span className="subtle">{label}</span>
        <span className="mono" style={{ fontWeight:600, color }}>{used}/{cap}h</span>
      </div>
      <div style={{ height:6, borderRadius:3, background:'var(--bg-subtle)', overflow:'hidden' }}>
        <div style={{ width:pct + '%', height:'100%', background:color, borderRadius:3 }}></div>
      </div>
    </div>
  );
}
function ReqStatusBadge({ status }){
  const { lang } = useLang();
  const s = HRM_REQ_STATUS[status] || HRM_REQ_STATUS.pending;
  return <span className={'badge ' + s.cls}><i className="dot"></i>{s[lang]}</span>;
}

/* ---------- module root ---------- */
function HrmModule(){
  const { p, canManage, myEmp, data } = useHrm();
  const { lang } = useLang();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const sub = p.route.sub || 'overview';
  const pendLeave = data.requests.filter(r => r.status === 'pending' && r.kind === 'leave').length;
  const pendOt = data.requests.filter(r => r.status === 'pending' && r.kind === 'ot').length;
  const items = [
    { id:'overview', icon:'bi-house', label:L('Tổng quan', 'Overview') },
    { id:'employees', icon:'bi-people', label:L('Hồ sơ nhân sự', 'Employees') },
    { id:'attendance', icon:'bi-clock-history', label:L('Chấm công', 'Attendance') },
    { id:'leave', icon:'bi-calendar-check', label:L('Nghỉ phép', 'Leave'), count:canManage && pendLeave ? pendLeave : null },
    { id:'overtime', icon:'bi-moon-stars', label:L('Làm thêm giờ', 'Overtime'), count:canManage && pendOt ? pendOt : null },
    { id:'payroll', icon:'bi-cash-stack', label:canManage ? L('Bảng lương', 'Payroll') : L('Phiếu lương', 'Payslips') },
    ...(canManage ? [{ id:'reports', icon:'bi-clipboard-data', label:L('Báo cáo', 'Reports') }] : []),
    { id:'settings', icon:'bi-gear', label:L('Thiết lập', 'Settings') },
  ];
  const go = (id) => p.navigate({ name:'module', module:'hrm', sub:id });
  return (<>
    <ModuleSidebar app={HRM_APP} items={items} active={sub} onNavigate={go} />
    <main className="main">
      {sub === 'overview' && <HrmOverview go={go} />}
      {sub === 'employees' && <HrmEmployees go={go} />}
      {sub === 'attendance' && <HrmAttendance />}
      {sub === 'leave' && <HrmLeave />}
      {sub === 'overtime' && <HrmOvertime />}
      {sub === 'payroll' && <HrmPayroll />}
      {sub === 'reports' && canManage && <HrmReports />}
      {sub === 'help' && <ModuleHelp moduleId="hrm" app={HRM_APP} />}
      {sub === 'settings' && <HrmSettings />}
    </main>
  </>);
}

/* ---------- thẻ chấm công (overview + widget dashboard — 1 chạm, mobile-first) ---------- */
// compact = bản gọn 1 hàng cho ô đầu dải KPI (Tổng quan) — giữ nguyên logic, chỉ đổi bố cục;
// nếu dựng bản đầy đủ vào dải KPI thì cả hàng KPI bị đội cao gấp đôi.
function CheckinCard({ compact }){
  const p = usePortal();
  const { lang } = useLang();
  const toast = useToast();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const myEmp = hrmMyEmployee(p.hrm.employees, p.me);
  const [now, setNow] = React.useState(() => new Date());
  React.useEffect(() => { const t = setInterval(() => setNow(new Date()), 1000); return () => clearInterval(t); }, []);
  if (!myEmp) return (
    <div className="flex items-center g10" style={{ color:'var(--text-muted)', padding:'6px 0' }}>
      <i className="bi bi-person-x" style={{ fontSize:20, flex:'none' }}></i>
      <div style={{ fontSize:'var(--fs-base)' }}>{L('Tài khoản chưa liên kết hồ sơ nhân sự — liên hệ HR để bắt đầu chấm công.', 'Your account is not linked to an employee record — contact HR to start clocking in.')}</div>
    </div>
  );
  const today = todayVN();
  const row = p.hrm.attendance.find(a => a.employeeId === myEmp.id && a.workDate === today);
  const clock = String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0') + ':' + String(now.getSeconds()).padStart(2, '0');
  const doIn = () => {
    p.hrmAdd('attendance', { id:hrmUid('cc'), employeeId:myEmp.id, workDate:today, checkIn:new Date().toISOString(), checkOut:null, source:'self', editedBy:null, note:'' });
    toast(L('Đã chấm công vào ca', 'Clocked in'), 'success');
  };
  const doOut = () => {
    p.hrmPatch('attendance', row.id, { ...row, checkOut:new Date().toISOString() });
    toast(L('Đã chấm công ra ca', 'Clocked out'), 'success');
  };
  const btnStyle = { minHeight:56, fontSize:'var(--fs-lg)', width:'100%', justifyContent:'center' };
  // Bản GỌN cho ô đầu dải KPI ở Dashboard: 1 hàng [giờ + trạng thái][nút] — cao ngang 1 ô KPI.
  if (compact) return (
    <div className="flex items-center g10" style={{ height:'100%', minWidth:0 }}>
      <div style={{ minWidth:0 }}>
        <div className="k-label"><i className="bi bi-fingerprint"></i>{L('Chấm công hôm nay', 'Attendance today')}</div>
        <div className="mono" style={{ fontSize:'var(--fs-2xl)', fontWeight:700, lineHeight:1.15, letterSpacing:.5 }}>{clock}</div>
        {row && <div className="subtle mono truncate" style={{ fontSize:'var(--fs-sm)' }}>
          {hrmTimeHM(row.checkIn)}{row.checkOut ? ' → ' + hrmTimeHM(row.checkOut) + ' · ' + hrmWorkedHours(row) + 'h' : L(' · đang trong ca', ' · on shift')}</div>}
      </div>
      <div className="grow"></div>
      {!row && <button className="btn btn-primary" style={{ minHeight:44, flex:'none' }} onClick={doIn}><i className="bi bi-box-arrow-in-right"></i>{L('Vào ca', 'In')}</button>}
      {row && !row.checkOut && <button className="btn btn-primary" style={{ minHeight:44, flex:'none' }} onClick={doOut}><i className="bi bi-box-arrow-right"></i>{L('Ra ca', 'Out')}</button>}
      {row && row.checkOut && <span className="badge badge-success" style={{ flex:'none' }}><i className="dot"></i>{L('Đã ra ca', 'Done')}</span>}
    </div>
  );
  return (
    <div className="col g10">
      <div className="flex items-center justify-between g10">
        <div>
          <div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{fmtDate(today)} · {myEmp.fullName}</div>
          <div className="mono" style={{ fontSize:24, fontWeight:700, letterSpacing:.5 }}>{clock}</div>
        </div>
        {!row && <span className="badge badge-neutral"><i className="dot"></i>{L('Chưa vào ca', 'Not in yet')}</span>}
        {row && !row.checkOut && <span className="badge badge-info"><i className="dot"></i>{L('Đang trong ca', 'On shift')}</span>}
        {row && row.checkOut && <span className="badge badge-success"><i className="dot"></i>{L('Đã ra ca', 'Done')}</span>}
      </div>
      {!row && <button className="btn btn-primary" style={btnStyle} onClick={doIn}><i className="bi bi-box-arrow-in-right"></i>{L('Chấm công vào', 'Clock in')}</button>}
      {row && !row.checkOut && (<>
        <div className="subtle" style={{ fontSize:'var(--fs-base)' }}>{L('Vào ca lúc ', 'Clocked in at ')}<b className="mono">{hrmTimeHM(row.checkIn)}</b></div>
        <button className="btn btn-primary" style={btnStyle} onClick={doOut}><i className="bi bi-box-arrow-right"></i>{L('Chấm công ra', 'Clock out')}</button>
      </>)}
      {row && row.checkOut && (
        <div className="flex items-center g8 mono" style={{ fontSize:'var(--fs-lg)', fontWeight:600 }}>
          {hrmTimeHM(row.checkIn)} <i className="bi bi-arrow-right subtle" style={{ fontSize:12 }}></i> {hrmTimeHM(row.checkOut)}
          <span className="badge badge-success" style={{ marginLeft:4 }}>{hrmWorkedHours(row)}h</span>
        </div>
      )}
    </div>
  );
}

/* ---------- Tổng quan ---------- */
function HrmOverview({ go }){
  const { p, st, myEmp, canManage, data } = useHrm();
  const { lang } = useLang();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const today = todayVN(), year = +today.slice(0, 4);
  const active = data.employees.filter(e => e.status !== 'terminated');
  const inToday = data.attendance.filter(a => a.workDate === today && a.checkIn).length;
  const onLeave = data.requests.filter(r => r.kind === 'leave' && r.status === 'approved' && r.startDate <= today && r.endDate >= today).length;
  const pending = data.requests.filter(r => r.status === 'pending');
  const myReqs = myEmp ? data.requests.filter(r => r.employeeId === myEmp.id).slice(0, 5) : [];
  const upcoming = data.holidays.filter(h => h.date >= today).sort((a, b) => a.date < b.date ? -1 : 1).slice(0, 4);
  const balance = myEmp ? hrmLeaveBalance(myEmp, data.requests, year, st) : 0;
  const ot = myEmp ? hrmOtTotals(data.requests, myEmp, today, st) : null;
  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{L('Nhân sự & Chấm công', 'People & Time')}</h1>
        <div className="sub">{L('Chấm công, nghỉ phép, làm thêm giờ và hồ sơ nhân sự theo Bộ luật Lao động.', 'Attendance, leave, overtime & employee records per the Labor Code.')}</div></div></div>
      {canManage && (
        <div className="kpi-strip">
          <Kpi label={L('Nhân sự đang làm', 'Active employees')} value={active.length} icon="bi-people" />
          <Kpi label={L('Đã vào ca hôm nay', 'Clocked in today')} value={inToday + '/' + active.length} icon="bi-clock-history" />
          <Kpi label={L('Đang nghỉ hôm nay', 'On leave today')} value={onLeave} icon="bi-calendar-x" />
          <Kpi label={L('Đơn chờ duyệt', 'Pending requests')} value={pending.length} icon="bi-hourglass-split" />
        </div>
      )}
      {/* KHÔNG dùng alignItems:'start' — thẻ sẽ co theo nội dung nên Chấm công / Của tôi / Ngày lễ
          mỗi cái một chiều cao, nhìn lởm chởm. Để mặc định `stretch` → mọi thẻ CÙNG hàng bằng nhau. */}
      <div className="grid-2col" style={{ display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(300px, 1fr))', gap:14 }}>
        <div className="card">
          <div className="card-head"><h3><i className="bi bi-fingerprint" style={{ marginRight:7, color:HRM_APP.color }}></i>{L('Chấm công hôm nay', 'Today attendance')}</h3></div>
          <div className="card-body"><CheckinCard /></div>
        </div>
        <div className="card">
          <div className="card-head"><h3><i className="bi bi-calendar-check" style={{ marginRight:7, color:'var(--success)' }}></i>{L('Của tôi', 'My summary')}</h3></div>
          <div className="card-body col g10">
            {myEmp ? (<>
              <div className="flex justify-between items-center"><span className="muted">{L('Phép năm còn lại', 'Leave balance')}</span>
                <span className="mono" style={{ fontWeight:700, fontSize:'var(--fs-lg)', color:'var(--primary)' }}>{balance} {L('ngày', 'days')}</span></div>
              {ot && <div className="flex g14 wrap"><OtMeter label={L('OT tháng này', 'OT this month')} {...ot.month} /><OtMeter label={L('OT năm ' + year, 'OT ' + year)} {...ot.year} /></div>}
              <div style={{ borderTop:'1px solid var(--border-light)', paddingTop:8 }}>
                <div className="subtle" style={{ fontSize:'var(--fs-sm)', marginBottom:6 }}>{L('Đơn gần đây của tôi', 'My recent requests')}</div>
                {myReqs.length === 0 && <div className="subtle" style={{ fontSize:'var(--fs-base)' }}>{L('Chưa có đơn nào', 'No requests yet')}</div>}
                {myReqs.map(r => (
                  <div key={r.id} className="flex items-center g8" style={{ padding:'4px 0' }}>
                    <span className="mono" style={{ fontWeight:600, color:'var(--primary)', fontSize:'var(--fs-base)' }}>{r.id}</span>
                    <span className="grow truncate" style={{ fontSize:'var(--fs-base)' }}>{r.kind === 'ot' ? L('Làm thêm ', 'OT ') + r.hours + 'h' : (HRM_LEAVE_TYPES[r.leaveType] || {})[lang] || r.leaveType}</span>
                    <ReqStatusBadge status={r.status} />
                  </div>
                ))}
              </div>
              <div className="flex g8">
                <button className="btn btn-sm" onClick={() => go('leave')}><i className="bi bi-plus-lg"></i>{L('Tạo đơn nghỉ', 'New leave')}</button>
                <button className="btn btn-sm" onClick={() => go('overtime')}><i className="bi bi-plus-lg"></i>{L('Đăng ký OT', 'New OT')}</button>
              </div>
            </>) : <div className="subtle">{L('Tài khoản chưa liên kết hồ sơ nhân sự.', 'Account not linked to an employee record.')}</div>}
          </div>
        </div>
        <div className="card">
          <div className="card-head"><h3><i className="bi bi-flag" style={{ marginRight:7, color:'var(--warning)' }}></i>{L('Ngày lễ sắp tới', 'Upcoming holidays')}</h3></div>
          <div className="card-body col g6">
            {upcoming.length === 0 && <div className="subtle">{L('Không còn ngày lễ trong năm', 'No more holidays this year')}</div>}
            {upcoming.map(h => (
              <div key={h.id} className="flex items-center g8" style={{ padding:'3px 0' }}>
                <span className="mono subtle" style={{ fontSize:'var(--fs-base)', flex:'none' }}>{fmtDate(h.date)}</span>
                <span className="grow truncate" style={{ fontWeight:500 }}>{h.name[lang] || h.name.vi}</span>
                {h.paid && <span className="badge badge-success">{L('Hưởng lương', 'Paid')}</span>}
              </div>
            ))}
          </div>
        </div>
        {canManage && <HrmRenewalCard />}
        {canManage && (
          <div className="card">
            <div className="card-head"><h3><i className="bi bi-inbox" style={{ marginRight:7, color:'var(--error)' }}></i>{L('Chờ duyệt', 'Pending queue')}</h3>
              {pending.length > 0 && <span className="badge badge-warning">{pending.length}</span>}</div>
            <div className="card-body col g6">
              {pending.length === 0 && <div className="subtle">{L('Không có đơn chờ duyệt', 'Nothing pending')}</div>}
              {pending.slice(0, 5).map(r => (
                <div key={r.id} className="flex items-center g8 clickable" style={{ padding:'3px 0', cursor:'pointer' }} onClick={() => go(r.kind === 'ot' ? 'overtime' : 'leave')}>
                  <span className="mono" style={{ fontWeight:600, color:'var(--primary)', fontSize:'var(--fs-base)' }}>{r.id}</span>
                  <span className="grow truncate" style={{ fontSize:'var(--fs-base)' }}>{hrmEmpName(p.hrm.employees, r.employeeId)}</span>
                  <span className="subtle mono" style={{ fontSize:'var(--fs-sm)' }}>{fmtDate(r.startDate)}</span>
                </div>
              ))}
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

/* ---------- Cụm HĐ tới hạn tái ký (Overview, chỉ quản lý) ----------
   Bám Điều 20 BLLĐ: hết hạn mà NLĐ vẫn làm việc thì trong 30 ngày phải ký HĐ mới, quá 30 ngày
   HĐ tự thành KHÔNG thời hạn; và chỉ được ký thêm 1 lần HĐ xác định thời hạn. */
function HrmRenewalCard(){
  const { p, data } = useHrm();
  const { lang } = useLang();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const [win, setWin] = React.useState(60);   // cửa sổ nhắc: 30 · 60 · 90 ngày
  const rows = React.useMemo(() => hrmContractRenewals(data.employees, data.contracts, win), [data.employees, data.contracts, win]);
  const over = rows.filter(r => r.daysLeft < 0).length;
  const dayText = (n) => n < 0 ? L('Quá hạn ' + (-n) + ' ngày', (-n) + 'd overdue')
    : n === 0 ? L('Hết hạn hôm nay', 'Ends today') : L('Còn ' + n + ' ngày', n + 'd left');
  return (
    // Chiếm TRỌN hàng của lưới Tổng quan: đây là cụm quản lý, không phải widget nhỏ. Ở bề rộng 1 cột
    // (300px) tên + số HĐ bị bóp mất hẳn, chỉ còn avatar + badge — vô dụng.
    <div className="card" style={{ gridColumn:'1 / -1' }}>
      <div className="card-head">
        <h3><i className="bi bi-arrow-repeat" style={{ marginRight:7, color:over ? 'var(--error)' : 'var(--warning)' }}></i>{L('HĐ tới hạn tái ký', 'Contracts due for renewal')}</h3>
        <div className="flex items-center g8">
          {rows.length > 0 && <span className={'badge ' + (over ? 'badge-error' : 'badge-warning')}>{rows.length}</span>}
          <div className="seg">{[30, 60, 90].map(d => (
            <button key={d} className={win === d ? 'is-active' : ''} onClick={() => setWin(d)}>{d}{L(' ngày', 'd')}</button>
          ))}</div>
        </div>
      </div>
      {/* danh sách dài → cuộn TRONG thẻ, giữ khung cố định (rule bố cục) */}
      <div className="card-body col g6" style={{ maxHeight:250, overflowY:'auto' }}>
        {rows.length === 0 && <div className="subtle">{L('Không có hợp đồng nào tới hạn trong ' + win + ' ngày tới.', 'No contracts due in the next ' + win + ' days.')}</div>}
        {rows.map(r => (
          // wrap + flexBasis: hẹp thì badge XUỐNG DÒNG, KHÔNG bóp chết cột tên (mobile 1 cột vẫn đọc được)
          <div key={r.contract.id} className="flex items-center g8 wrap" style={{ padding:'4px 0', cursor:'pointer', borderBottom:'1px solid var(--border-light)' }}
            onClick={() => p.navigate({ name:'module', module:'hrm', sub:'employees', id:r.employee.id })}>
            <Avatar name={r.employee.fullName} size={24} />
            <div style={{ flex:'1 1 180px', minWidth:0 }}>
              <div className="truncate" style={{ fontWeight:500 }}>{r.employee.fullName}
                <span className="subtle mono" style={{ fontSize:'var(--fs-sm)', marginLeft:6 }}>{r.employee.id}</span></div>
              <div className="subtle truncate" style={{ fontSize:'var(--fs-sm)' }}>
                {r.contract.contractNo || r.contract.id} · {L('hết hạn ', 'ends ')}{fmtDate(r.contract.endDate)}
                {r.employee.dept ? ' · ' + r.employee.dept : ''}</div>
            </div>
            <div className="flex items-center g6" style={{ flex:'none' }}>
              {r.autoIndefinite && <span className="badge badge-error" title={L('Điều 20 BLLĐ: quá 30 ngày chưa ký HĐ mới thì HĐ đã ký trở thành không xác định thời hạn.', 'Art. 20: after 30 days it becomes indefinite by law.')}>{L('Đã thành vô thời hạn', 'Now indefinite')}</span>}
              {!r.autoIndefinite && r.mustIndefinite && <span className="badge badge-warning" title={L('Điều 20 BLLĐ: chỉ được ký thêm 1 lần HĐ xác định thời hạn.', 'Art. 20: only one renewal allowed.')}>{L('Phải ký vô thời hạn', 'Must be indefinite')}</span>}
              <span className={'badge ' + (r.level === 'over' ? 'badge-error' : r.level === 'urgent' ? 'badge-warning' : 'badge-neutral')}>{dayText(r.daysLeft)}</span>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

/* ---------- Hồ sơ nhân sự (list; chi tiết ở hrm-profile.jsx) ---------- */
/* Lọc nhanh bằng NÚT (giống Khoản chi ở Tài chính): đếm CHÉO — số trên chip phòng ban tính theo
   trạng thái đang chọn và ngược lại → số trên chip LUÔN = số hàng lọc ra. Chỉ hiện mục CÓ dữ liệu
   (chip lọc ra 0 hàng là vô dụng) nhưng luôn giữ mục đang chọn để không kẹt màn trống khi facet
   kia đổi. `rows` là danh sách ĐÃ lọc theo ô tìm kiếm → số đếm khớp cả tìm kiếm. */
function HrmListFilters({ rows, dept, setDept, status, setStatus }){
  const { lang } = useLang();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const byStatus = rows.filter(e => status === 'all' || e.status === status);
  const byDept = rows.filter(e => dept === 'all' || e.dept === dept);
  const tally = (arr, k) => arr.reduce((m, e) => { const v = e[k]; if (v) m[v] = (m[v] || 0) + 1; return m; }, {});
  const dN = tally(byStatus, 'dept'), sN = tally(byDept, 'status');
  const depts = [...new Set(rows.map(e => e.dept).filter(Boolean))].sort().filter(d => dN[d] || d === dept);
  const stats = Object.keys(HRM_STATUS).filter(s => sN[s] || s === status);
  if (depts.length < 2 && stats.length < 2) return null;   // 1 phòng ban + 1 trạng thái thì lọc vô nghĩa
  return (<>
    <div className="seg" style={{ flexWrap:'wrap' }}>
      <button className={dept === 'all' ? 'is-active' : ''} onClick={() => setDept('all')}>
        <i className="bi bi-diagram-3"></i>{L('Tất cả phòng ban', 'All depts')}<span className="badge badge-neutral">{byStatus.length}</span></button>
      {depts.map(d => <button key={d} className={dept === d ? 'is-active' : ''} onClick={() => setDept(d)}>{d}<span className="badge badge-neutral">{dN[d] || 0}</span></button>)}
    </div>
    <div className="seg" style={{ flexWrap:'wrap' }}>
      <button className={status === 'all' ? 'is-active' : ''} onClick={() => setStatus('all')}>
        <i className="bi bi-people"></i>{L('Mọi trạng thái', 'All statuses')}<span className="badge badge-neutral">{byDept.length}</span></button>
      {stats.map(s => <button key={s} className={status === s ? 'is-active' : ''} onClick={() => setStatus(s)}>{HRM_STATUS[s][lang]}<span className="badge badge-neutral">{sN[s] || 0}</span></button>)}
    </div>
  </>);
}

function HrmEmployees({ go }){
  const { p, st, canManage, myEmp, data } = useHrm();
  const { lang } = useLang();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  // filter sống qua chuyển tab module (useTabState — quay lại vẫn đúng bộ lọc đang xem)
  const [q, setQ] = useTabState('hrm', 'emp.q', '');
  const [dept, setDept] = useTabState('hrm', 'emp.dept', 'all');
  const [status, setStatus] = useTabState('hrm', 'emp.status', 'all');
  const [formOpen, setFormOpen] = React.useState(false);
  const [scan, setScan] = React.useState(false);
  const [prefill, setPrefill] = React.useState(null);   // thông tin đọc từ thẻ CCCD → mở form đã điền sẵn
  const srt = useTableSort();
  if (p.route.id) return <HrmEmployeeProfile empId={p.route.id} onBack={() => p.navigate({ name:'module', module:'hrm', sub:'employees' })} />;
  const openable = (id) => canManage || (myEmp && myEmp.id === id);
  const open = (id) => { if (openable(id)) p.navigate({ name:'module', module:'hrm', sub:'employees', id }); };

  if (!canManage) {
    // Nhân viên: danh bạ rút gọn (không lương/CCCD) + mở được hồ sơ CHÍNH MÌNH
    const found = p.hrmDir.filter(e => (e.fullName + e.id + e.dept + e.position + e.email).toLowerCase().includes(q.toLowerCase()));
    const list = srt.sortRows(hrmFilterEmployees(found, dept, status),
      { id:e => e.id, name:e => e.fullName, pos:e => e.position });
    // Gom nhóm theo phòng ban (cột Phòng ban bỏ đi — đã là tiêu đề nhóm). Không dùng hook: nhánh có điều kiện.
    const groups = [];
    list.forEach(e => {
      const k = e.dept || L('Chưa phân phòng', 'Unassigned');
      const g = groups.find(x => x.dept === k) || (groups.push({ dept:k, rows:[] }), groups[groups.length - 1]);
      g.rows.push(e);
    });
    groups.sort((a, b) => a.dept.localeCompare(b.dept, 'vi'));
    const stop = (ev) => ev.stopPropagation();   // liên kết mail/tel không kích mở hồ sơ
    return (
      <div className="page pagefade">
        <div className="page-head"><div><h1>{L('Danh bạ nhân sự', 'People directory')}</h1>
          <div className="sub">{L('Danh bạ rút gọn. Bạn chỉ xem được hồ sơ đầy đủ của chính mình.', 'Reduced directory. You can only open your own full record.')}</div></div>
          {myEmp && <button className="btn btn-primary" onClick={() => open(myEmp.id)}><i className="bi bi-person-badge"></i>{L('Hồ sơ của tôi', 'My record')}</button>}</div>
        <div className="toolbar">
          <input className="input" style={{ maxWidth:280 }} placeholder={L('Tìm tên, phòng ban…', 'Search…')} value={q} onChange={e => setQ(e.target.value)} />
          <HrmListFilters rows={found} dept={dept} setDept={setDept} status={status} setStatus={setStatus} />
        </div>
        <div className="card"><div style={{ overflow:'auto' }}>
          <table className="tbl">
            <thead><tr><ThSort k="id" sort={srt}>{L('Mã', 'ID')}</ThSort><ThSort k="name" sort={srt}>{L('Họ tên', 'Name')}</ThSort><ThSort k="pos" sort={srt}>{L('Vị trí', 'Position')}</ThSort><th>{L('Email', 'Email')}</th><th>{L('Điện thoại', 'Phone')}</th></tr></thead>
            <tbody>{groups.map(g => (
              <React.Fragment key={g.dept}>
                <tr style={{ cursor:'default', background:'var(--bg-subtle)' }}>
                  <td colSpan={5} style={{ fontWeight:600 }}>
                    <span className="flex items-center g8 wrap">{g.dept}<span className="badge badge-neutral">{g.rows.length}</span>
                      {hrmDeptManagers(st, p.hrmDir, g.dept).map(m => (
                        <span key={m.id} className="subtle" style={{ fontWeight:400, fontSize:'var(--fs-sm)' }}>
                          <i className="bi bi-person-badge" style={{ marginRight:3 }}></i>{m.fullName} · {m.position}</span>))}
                    </span>
                  </td>
                </tr>
                {g.rows.map(e => (
                  <tr key={e.id} style={{ cursor:openable(e.id) ? 'pointer' : 'default' }} onClick={() => open(e.id)}>
                    <td className="mono" style={{ fontWeight:600, color:'var(--primary)' }}>{e.id}</td>
                    <td style={{ fontWeight:500 }}><span className="flex items-center g8"><Avatar name={e.fullName} size={22} />{e.fullName}{myEmp && myEmp.id === e.id && <span className="badge badge-info">{L('Tôi', 'Me')}</span>}</span></td>
                    <td className="muted">{e.position}</td>
                    <td className="muted">{e.email ? <a href={'mailto:' + e.email} onClick={stop}>{e.email}</a> : '—'}</td>
                    <td className="muted mono">{e.phone ? <a href={'tel:' + e.phone.replace(/\s/g, '')} onClick={stop}>{e.phone}</a> : '—'}</td>
                  </tr>))}
              </React.Fragment>))}</tbody>
          </table>
        </div></div>
      </div>
    );
  }

  const found = data.employees.filter(e => (e.fullName + e.id + e.dept + e.position).toLowerCase().includes(q.toLowerCase()));
  const list = srt.sortRows(hrmFilterEmployees(found, dept, status),
    { id:e => e.id, name:e => e.fullName, dept:e => e.dept, pos:e => e.position, start:e => e.startDate, status:e => e.status });
  const curContract = (empId) => {
    const cs = data.contracts.filter(c => c.employeeId === empId && c.kind !== 'appendix' && c.status === 'active');
    return cs[cs.length - 1] || null;
  };
  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{L('Hồ sơ nhân sự', 'Employees')}</h1>
        <div className="sub">{L('Sổ quản lý lao động điện tử (Điều 3 NĐ 145/2020).', 'Electronic employee register (Art. 3, Decree 145/2020).')}</div></div>
        <div className="flex g8">
          <button className="btn" onClick={() => setScan(true)}><i className="bi bi-qr-code-scan"></i>{L('Thêm nhanh từ CCCD', 'Quick add from ID')}</button>
          <button className="btn btn-primary" onClick={() => setFormOpen(true)}><i className="bi bi-person-plus"></i>{L('Thêm nhân viên', 'New employee')}</button>
        </div></div>
      <div className="toolbar">
        <input className="input" style={{ maxWidth:280 }} placeholder={L('Tìm tên, mã, phòng ban…', 'Search…')} value={q} onChange={e => setQ(e.target.value)} />
        <HrmListFilters rows={found} dept={dept} setDept={setDept} status={status} setStatus={setStatus} />
      </div>
      <div className="card"><div style={{ overflow:'auto' }}>
        <table className="tbl">
          <thead><tr><ThSort k="id" sort={srt}>{L('Mã', 'ID')}</ThSort><ThSort k="name" sort={srt}>{L('Họ tên', 'Name')}</ThSort><ThSort k="pos" sort={srt}>{L('Vị trí', 'Position')}</ThSort><ThSort k="dept" sort={srt}>{L('Phòng ban', 'Dept')}</ThSort><ThSort k="start" sort={srt}>{L('Ngày vào', 'Start date')}</ThSort><th>{L('HĐ hiện tại', 'Contract')}</th><ThSort k="status" sort={srt}>{L('Trạng thái', 'Status')}</ThSort></tr></thead>
          <tbody>
            {list.length === 0 && <tr><td colSpan="7"><EmptyState icon="bi-person-x" text={L('Không có nhân sự khớp bộ lọc.', 'No employees match the filters.')} /></td></tr>}
            {list.map(e => { const c = curContract(e.id); const stt = HRM_STATUS[e.status] || HRM_STATUS.active; return (
            <tr key={e.id} style={{ cursor:'pointer' }} onClick={() => open(e.id)}>
              <td className="mono" style={{ fontWeight:600, color:'var(--primary)' }}>{e.id}</td>
              <td style={{ fontWeight:500 }}><span className="flex items-center g8"><Avatar name={e.fullName} size={22} src={e.photo} />{e.fullName}</span></td>
              <td className="muted">{e.position}</td><td className="muted">{e.dept}</td>
              <td className="mono" style={{ fontSize:'var(--fs-base)' }}>{fmtDate(e.startDate)}</td>
              <td>{c ? <span className={'badge ' + (HRM_CONTRACT_KINDS[c.kind] || {}).cls}>{(HRM_CONTRACT_KINDS[c.kind] || {})[lang]}</span> : <span className="subtle">—</span>}</td>
              <td><span className={'badge ' + stt.cls}><i className="dot"></i>{stt[lang]}</span></td>
            </tr>); })}</tbody>
        </table>
      </div></div>
      {/* Quét từ danh sách: đọc thẻ xong mở LUÔN form thêm nhân viên đã điền sẵn (onboard nhiều người) */}
      {scan && <CccdScanModal onClose={() => setScan(false)} onDone={(i) => { setPrefill(i); setFormOpen(true); }} />}
      {formOpen && <HrmEmployeeFormModal cccd={prefill} onClose={() => { setFormOpen(false); setPrefill(null); }}
        onCreated={(id) => { setFormOpen(false); setPrefill(null); open(id); }} />}
    </div>
  );
}

/* ---------- Chấm công ---------- */
function HrmAttendance(){
  const { p, canManage, myEmp, data } = useHrm();
  const { lang } = useLang();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const [ym, setYm] = React.useState(() => todayVN().slice(0, 7));
  const [edit, setEdit] = React.useState(null);   // {emp, date, row|null}
  const holSet = new Set(data.holidays.map(h => h.date));
  const dates = hrmMonthDates(ym);
  const shiftMonth = (d) => { const dt = new Date(+ym.slice(0, 4), +ym.slice(5) - 1 + d, 1); setYm(dt.getFullYear() + '-' + String(dt.getMonth() + 1).padStart(2, '0')); };
  const monthNav = (
    <div className="flex items-center g8">
      <button className="btn btn-sm" onClick={() => shiftMonth(-1)}><i className="bi bi-chevron-left"></i></button>
      <span className="mono" style={{ fontWeight:700, minWidth:76, textAlign:'center' }}>{ym.slice(5)}/{ym.slice(0, 4)}</span>
      <button className="btn btn-sm" onClick={() => shiftMonth(1)}><i className="bi bi-chevron-right"></i></button>
    </div>
  );

  // ----- nhân viên: lịch sử của tôi -----
  if (!canManage) {
    const mine = myEmp ? data.attendance.filter(a => a.employeeId === myEmp.id && a.workDate.slice(0, 7) === ym).sort((a, b) => a.workDate < b.workDate ? 1 : -1) : [];
    const totH = mine.reduce((s, r) => s + hrmWorkedHours(r), 0);
    return (
      <div className="page pagefade">
        <div className="page-head"><div><h1>{L('Chấm công của tôi', 'My attendance')}</h1>
          <div className="sub">{L('Lịch sử vào/ra ca theo tháng.', 'Monthly clock-in/out history.')}</div></div>{monthNav}</div>
        <div className="card" style={{ marginBottom:14 }}><div className="card-body"><CheckinCard /></div></div>
        <div className="card"><div style={{ overflow:'auto' }}>
          <table className="tbl">
            <thead><tr><th>{L('Ngày', 'Date')}</th><th>{L('Vào ca', 'In')}</th><th>{L('Ra ca', 'Out')}</th><th className="num">{L('Giờ công', 'Hours')}</th><th>{L('Ghi chú', 'Note')}</th></tr></thead>
            <tbody>
              {mine.length === 0 && <tr><td colSpan="5"><EmptyState icon="bi-clock-history" text={L('Chưa có dữ liệu chấm công tháng này', 'No attendance this month')} /></td></tr>}
              {mine.map(r => { const h = hrmWorkedHours(r); return (
                <tr key={r.id} style={{ cursor:'default' }}>
                  <td className="mono">{fmtDate(r.workDate)}{holSet.has(r.workDate) && <span className="badge badge-warning" style={{ marginLeft:6 }}>{L('Lễ', 'Holiday')}</span>}</td>
                  <td className="mono">{hrmTimeHM(r.checkIn)}</td><td className="mono">{hrmTimeHM(r.checkOut)}</td>
                  <td className="num mono" style={{ fontWeight:600, color:h > 12 ? 'var(--error)' : 'inherit' }}>{r.checkOut ? h + 'h' : '—'}</td>
                  <td className="muted" style={{ fontSize:'var(--fs-base)' }}>{r.source === 'manager' && <span className="badge badge-neutral" style={{ marginRight:6 }}>{L('QL sửa', 'Edited')}</span>}{r.note}</td>
                </tr>); })}
            </tbody>
            {mine.length > 0 && <tfoot><tr style={{ cursor:'default', background:'var(--bg-subtle)' }}><td style={{ fontWeight:700 }}>{L('Tổng', 'Total')}</td><td></td><td></td><td className="num mono" style={{ fontWeight:700 }}>{Math.round(totH * 10) / 10}h</td><td></td></tr></tfoot>}
          </table>
        </div></div>
      </div>
    );
  }

  // ----- quản lý: lưới NV × ngày, click ô để sửa/bổ sung -----
  const emps = data.employees.filter(e => e.status !== 'terminated');
  const attMap = {};
  data.attendance.forEach(a => { if (a.workDate.slice(0, 7) === ym) attMap[a.employeeId + '|' + a.workDate] = a; });
  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{L('Bảng chấm công', 'Attendance sheet')}</h1>
        <div className="sub">{L('Bấm vào ô để sửa/bổ sung công (ghi vết người sửa). Đỏ = ngày trên 12h (vượt trần Đ107 BLLĐ).', 'Click a cell to edit. Red = over 12h/day (Art. 107 cap).')}</div></div>{monthNav}</div>
      <div className="card" style={{ marginBottom:14 }}><div className="card-body"><CheckinCard /></div></div>
      <div className="card"><div style={{ overflow:'auto' }}>
        <table className="tbl" style={{ fontSize:'var(--fs-sm)' }}>
          <thead><tr>
            <th style={{ position:'sticky', left:0, background:'var(--bg)', zIndex:1, minWidth:150 }}>{L('Nhân viên', 'Employee')}</th>
            {dates.map(d => { const dow = new Date(d + 'T00:00:00').getDay(); return (
              <th key={d} className="num" style={{ padding:'6px 4px', minWidth:34, color:holSet.has(d) ? 'var(--warning)' : (dow === 0 || dow === 6) ? 'var(--text-muted)' : undefined }}>{+d.slice(8)}</th>); })}
            <th className="num" style={{ minWidth:52 }}>{L('Tổng', 'Total')}</th>
          </tr></thead>
          <tbody>
            {emps.map(e => {
              let tot = 0;
              return (
                <tr key={e.id} style={{ cursor:'default' }}>
                  <td style={{ position:'sticky', left:0, background:'var(--bg)', zIndex:1, fontWeight:500, whiteSpace:'nowrap' }}>{e.fullName}</td>
                  {dates.map(d => {
                    const r = attMap[e.id + '|' + d]; const h = r ? hrmWorkedHours(r) : 0; tot += h;
                    const dow = new Date(d + 'T00:00:00').getDay();
                    const bg = holSet.has(d) ? 'color-mix(in srgb, var(--warning) 9%, transparent)' : (dow === 0 || dow === 6) ? 'var(--bg-subtle)' : undefined;
                    return (
                      <td key={d} className="num mono" title={r ? hrmTimeHM(r.checkIn) + ' → ' + hrmTimeHM(r.checkOut) + (r.note ? ' · ' + r.note : '') : L('Bấm để bổ sung', 'Click to add')}
                        style={{ padding:'6px 4px', cursor:'pointer', background:bg, color:h > 12 ? 'var(--error)' : undefined, fontWeight:h > 12 ? 700 : undefined }}
                        onClick={() => setEdit({ emp:e, date:d, row:r || null })}>
                        {r ? (r.checkOut ? h : <i className="bi bi-play-circle" style={{ color:'var(--info, var(--primary))' }}></i>) : ''}
                      </td>);
                  })}
                  <td className="num mono" style={{ fontWeight:700 }}>{Math.round(tot * 10) / 10}</td>
                </tr>);
            })}
          </tbody>
        </table>
      </div></div>
      {edit && <HrmAttEditModal ctx={edit} onClose={() => setEdit(null)} />}
    </div>
  );
}

// Sửa/bổ sung 1 ô công (quản lý) — ghi vết edited_by ở DB (trigger), UI đặt source='manager'
function HrmAttEditModal({ ctx, onClose }){
  const p = usePortal();
  const { lang } = useLang();
  const toast = useToast();
  const confirm = useConfirm();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const { emp, date, row } = ctx;
  const [f, setF] = React.useState({ in:row && row.checkIn ? hrmTimeHM(row.checkIn) : '08:00', out:row && row.checkOut ? hrmTimeHM(row.checkOut) : '17:30', note:(row && row.note) || '' });
  const save = () => {
    if (f.out && f.in && f.out <= f.in) { toast(L('Giờ ra phải sau giờ vào', 'Clock-out must be after clock-in'), 'error'); return; }
    const next = { id:row ? row.id : hrmUid('cc'), employeeId:emp.id, workDate:date,
      checkIn:hrmIsoFrom(date, f.in), checkOut:f.out ? hrmIsoFrom(date, f.out) : null,
      source:row ? row.source : 'manager', editedBy:p.me.id, note:f.note };
    if (row) p.hrmPatch('attendance', row.id, next); else p.hrmAdd('attendance', next);
    toast(L('Đã lưu công', 'Saved'), 'success'); onClose();
  };
  const del = async () => {
    if (!(await confirm({ message:L('Xóa dòng công ngày ' + fmtDate(date) + ' của ' + emp.fullName + '?', 'Delete this attendance row?'), confirmLabel:L('Xóa', 'Delete') }))) return;
    p.hrmRemove('attendance', row.id); toast(L('Đã xóa', 'Deleted'), 'success'); onClose();
  };
  return (
    <Modal title={L('Công ngày ', 'Attendance ') + fmtDate(date) + ' · ' + emp.fullName} width={440} onClose={onClose}
      footer={<>
        {row && <button className="btn btn-danger" style={{ marginRight:'auto' }} onClick={del}><i className="bi bi-trash"></i>{L('Xóa', 'Delete')}</button>}
        <button className="btn" onClick={onClose}>{L('Hủy', 'Cancel')}</button>
        <button className="btn btn-primary" onClick={save}>{L('Lưu', 'Save')}</button></>}>
      <div className="col g16">
        <div className="flex g12">
          <div style={{ flex:1 }}><Field label={L('Giờ vào', 'Clock in')}><input type="time" className="input" value={f.in} onChange={e => setF(s => ({ ...s, in:e.target.value }))} /></Field></div>
          <div style={{ flex:1 }}><Field label={L('Giờ ra', 'Clock out')}><input type="time" className="input" value={f.out} onChange={e => setF(s => ({ ...s, out:e.target.value }))} /></Field></div>
        </div>
        <Field label={L('Ghi chú', 'Note')}><input className="input" value={f.note} onChange={e => setF(s => ({ ...s, note:e.target.value }))} placeholder={L('Lý do sửa/bổ sung…', 'Reason…')} /></Field>
      </div>
    </Modal>
  );
}

/* ---------- Đơn từ (bảng dùng chung nghỉ phép + OT) ---------- */
function HrmReqTable({ rows, showEmp, kind }){
  const { p, st, canManage, data } = useHrm();
  const { lang } = useLang();
  const toast = useToast();
  const confirm = useConfirm();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const [rejecting, setRejecting] = React.useState(null);
  const decide = (r, status, note) => {
    p.hrmPatch('requests', r.id, { ...r, status, decidedBy:p.me.id, decidedAt:new Date().toISOString(), decidedNote:note || '' });
    toast(status === 'approved' ? L('Đã duyệt ' + r.id, 'Approved') : L('Đã từ chối ' + r.id, 'Rejected'), 'success');
  };
  const approve = async (r) => {
    if (r.kind === 'ot') {
      const emp = data.employees.find(e => e.id === r.employeeId);
      const ot = hrmOtTotals(data.requests, emp, r.startDate, st);
      const mAfter = ot.month.used + (+r.hours || 0), yAfter = ot.year.used + (+r.hours || 0);
      if (mAfter > ot.month.cap || yAfter > ot.year.cap) {
        const ok = await confirm({ title:L('Vượt trần làm thêm giờ', 'Overtime cap exceeded'),
          message:L('Duyệt đơn này sẽ đưa tổng OT của ' + (emp ? emp.fullName : r.employeeId) + ' lên ' + mAfter + 'h/tháng (trần ' + ot.month.cap + 'h) và ' + yAfter + 'h/năm (trần ' + ot.year.cap + 'h). Điều 107 BLLĐ 2019 giới hạn 40h/tháng, 200h/năm (300h ngành đặc thù; vượt 200h phải thông báo Sở Nội vụ). Bạn chịu trách nhiệm khi duyệt vượt trần.',
            'Approving exceeds the legal OT caps (Art. 107 Labor Code: 40h/month, 200h/year). You accept responsibility.'),
          confirmLabel:L('Vẫn duyệt', 'Approve anyway') });
        if (!ok) return;
      }
    }
    decide(r, 'approved');
  };
  const cancel = async (r) => {
    if (!(await confirm({ message:L('Hủy đơn ' + r.id + '?', 'Cancel request ' + r.id + '?'), confirmLabel:L('Hủy đơn', 'Cancel it') }))) return;
    p.hrmPatch('requests', r.id, { ...r, status:'cancelled' });
    toast(L('Đã hủy đơn', 'Request cancelled'), 'success');
  };
  const myEmpId = (hrmMyEmployee(data.employees, p.me) || {}).id;
  return (<>
    <div style={{ overflow:'auto' }}>
      <table className="tbl">
        <thead><tr><th>{L('Mã', 'ID')}</th>{showEmp && <th>{L('Nhân viên', 'Employee')}</th>}
          <th>{kind === 'ot' ? L('Thời gian', 'Time') : L('Loại nghỉ', 'Type')}</th>
          <th>{L('Từ ngày', 'From')}</th><th>{L('Đến ngày', 'To')}</th>
          <th className="num">{kind === 'ot' ? L('Số giờ', 'Hours') : L('Số ngày', 'Days')}</th>
          <th>{L('Lý do', 'Reason')}</th><th>{L('Trạng thái', 'Status')}</th><th style={{ width:120 }}></th></tr></thead>
        <tbody>
          {rows.length === 0 && <tr><td colSpan={showEmp ? 9 : 8}><EmptyState icon="bi-inbox" text={L('Chưa có đơn nào', 'No requests')} /></td></tr>}
          {rows.map(r => (
            <tr key={r.id} style={{ cursor:'default' }}>
              <td className="mono" style={{ fontWeight:600, color:'var(--primary)' }}>{r.id}</td>
              {showEmp && <td style={{ fontWeight:500 }}>{hrmEmpName(data.employees, r.employeeId)}</td>}
              <td>{r.kind === 'ot'
                ? <span className="mono" style={{ fontSize:'var(--fs-base)' }}>{r.startTime}–{r.endTime}{r.isNight && <span className="badge badge-info" style={{ marginLeft:6 }}><i className="bi bi-moon"></i>{L('Đêm', 'Night')}</span>}</span>
                : <span>{(HRM_LEAVE_TYPES[r.leaveType] || {})[lang] || r.leaveType}</span>}</td>
              <td className="mono" style={{ fontSize:'var(--fs-base)' }}>{fmtDate(r.startDate)}</td>
              <td className="mono" style={{ fontSize:'var(--fs-base)' }}>{fmtDate(r.endDate)}</td>
              <td className="num mono" style={{ fontWeight:600 }}>{r.kind === 'ot' ? r.hours + 'h' : r.days}</td>
              <td className="muted truncate" style={{ maxWidth:220 }} title={r.reason}>{r.reason}{r.decidedNote && <div className="subtle" style={{ fontSize:'var(--fs-sm)' }}><i className="bi bi-reply" style={{ marginRight:3 }}></i>{r.decidedNote}</div>}</td>
              <td><ReqStatusBadge status={r.status} /></td>
              <td style={{ whiteSpace:'nowrap', textAlign:'right' }}>
                {r.status === 'pending' && canManage && (<>
                  <button className="btn btn-sm btn-primary" style={{ marginRight:4 }} onClick={() => approve(r)}><i className="bi bi-check-lg"></i>{L('Duyệt', 'OK')}</button>
                  <button className="btn btn-sm" onClick={() => setRejecting(r)}><i className="bi bi-x-lg"></i></button>
                </>)}
                {r.status === 'pending' && !canManage && r.employeeId === myEmpId &&
                  <button className="btn btn-sm" onClick={() => cancel(r)}><i className="bi bi-x-lg"></i>{L('Hủy', 'Cancel')}</button>}
              </td>
            </tr>))}
        </tbody>
      </table>
    </div>
    {rejecting && <HrmRejectModal req={rejecting} onReject={(note) => { decide(rejecting, 'rejected', note); setRejecting(null); }} onClose={() => setRejecting(null)} />}
  </>);
}
function HrmRejectModal({ req, onReject, onClose }){
  const { lang } = useLang();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const [note, setNote] = React.useState('');
  return (
    <Modal title={L('Từ chối đơn ', 'Reject ') + req.id} width={420} onClose={onClose}
      footer={<><button className="btn" onClick={onClose}>{L('Hủy', 'Cancel')}</button>
        <button className="btn btn-danger" onClick={() => onReject(note)}>{L('Từ chối', 'Reject')}</button></>}>
      <Field label={L('Lý do từ chối (gửi cho nhân viên)', 'Reason (sent to employee)')}>
        <textarea className="input" rows={3} value={note} onChange={e => setNote(e.target.value)} placeholder={L('Ví dụ: trùng lịch dự án, cần bàn giao trước…', 'e.g. project conflict…')} />
      </Field>
    </Modal>
  );
}

/* ---------- Nghỉ phép ---------- */
function HrmLeave(){
  const { p, st, myEmp, canManage, data } = useHrm();
  const { lang } = useLang();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const [open, setOpen] = React.useState(false);
  const year = +todayVN().slice(0, 4);
  const leaves = data.requests.filter(r => r.kind === 'leave');
  const pending = leaves.filter(r => r.status === 'pending');
  const mine = myEmp ? leaves.filter(r => r.employeeId === myEmp.id) : [];
  const quota = myEmp ? hrmLeaveQuota(myEmp, year, st) : 0;
  const used = myEmp ? hrmLeaveUsed(data.requests, myEmp.id, year) : 0;
  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{L('Nghỉ phép', 'Leave')}</h1>
        <div className="sub">{L('Phép năm 12 ngày pro-rata + 1 ngày/5 năm thâm niên (Đ113–114 BLLĐ); ốm đau do BHXH chi trả, không trừ phép.', 'Annual leave per Labor Code Art. 113–114; SI-paid sick leave does not deduct.')}</div></div>
        <button className="btn btn-primary" onClick={() => setOpen(true)}><i className="bi bi-plus-lg"></i>{L('Tạo đơn nghỉ', 'New leave request')}</button></div>
      {myEmp && (
        <div className="kpi-strip">
          <Kpi label={L('Quỹ phép ' + year, 'Quota ' + year)} value={quota + ' ' + L('ngày', 'd')} icon="bi-calendar3" />
          <Kpi label={L('Đã dùng', 'Used')} value={used + ' ' + L('ngày', 'd')} icon="bi-calendar-minus" />
          <Kpi label={L('Còn lại', 'Balance')} value={(quota - used) + ' ' + L('ngày', 'd')} icon="bi-calendar-check" />
          <Kpi label={L('Thâm niên', 'Seniority')} value={hrmYearsOfService(myEmp, todayVN()) + ' ' + L('năm', 'yrs')} icon="bi-award" delta={'+' + Math.floor(hrmYearsOfService(myEmp, year + '-12-31') / 5) + ' ' + L('ngày phép', 'bonus days')} deltaDir="up" />
        </div>
      )}
      {canManage && (
        <div className="card" style={{ marginBottom:14 }}>
          <div className="card-head"><h3>{L('Chờ duyệt', 'Pending')}</h3>{pending.length > 0 && <span className="badge badge-warning">{pending.length}</span>}</div>
          <HrmReqTable rows={pending} showEmp kind="leave" />
        </div>
      )}
      <div className="card" style={{ marginBottom:14 }}>
        <div className="card-head"><h3>{canManage ? L('Tất cả đơn nghỉ', 'All leave requests') : L('Đơn của tôi', 'My requests')}</h3></div>
        <HrmReqTable rows={canManage ? leaves.filter(r => r.status !== 'pending') : mine} showEmp={canManage} kind="leave" />
      </div>
      {canManage && (
        <div className="card">
          <div className="card-head"><h3>{L('Số dư phép toàn công ty · ' + year, 'Company balances · ' + year)}</h3></div>
          <div style={{ overflow:'auto' }}>
            <table className="tbl">
              <thead><tr><th>{L('Nhân viên', 'Employee')}</th><th className="num">{L('Quỹ phép', 'Quota')}</th><th className="num">{L('Đã dùng', 'Used')}</th><th className="num">{L('Còn lại', 'Balance')}</th></tr></thead>
              <tbody>{data.employees.filter(e => e.status !== 'terminated').map(e => {
                const q = hrmLeaveQuota(e, year, st), u = hrmLeaveUsed(data.requests, e.id, year);
                return (<tr key={e.id} style={{ cursor:'default' }}>
                  <td style={{ fontWeight:500 }}>{e.fullName}</td>
                  <td className="num mono">{q}</td><td className="num mono">{u}</td>
                  <td className="num mono" style={{ fontWeight:700, color:q - u <= 0 ? 'var(--error)' : 'var(--primary)' }}>{q - u}</td>
                </tr>); })}</tbody>
            </table>
          </div>
        </div>
      )}
      {open && <NewHrmRequestModal kind="leave" onClose={() => setOpen(false)} />}
    </div>
  );
}

/* ---------- Làm thêm giờ ---------- */
function HrmOvertime(){
  const { p, st, myEmp, canManage, data } = useHrm();
  const { lang } = useLang();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const [open, setOpen] = React.useState(false);
  const today = todayVN();
  const ots = data.requests.filter(r => r.kind === 'ot');
  const pending = ots.filter(r => r.status === 'pending');
  const mine = myEmp ? ots.filter(r => r.employeeId === myEmp.id) : [];
  const ot = myEmp ? hrmOtTotals(data.requests, myEmp, today, st) : null;
  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{L('Làm thêm giờ', 'Overtime')}</h1>
        <div className="sub">{L('Trần luật: 40h/tháng · 200h/năm (300h ngành đặc thù) — Đ107 BLLĐ. Mỗi đợt OT cần nhân viên đồng ý (Đ59 NĐ145).', 'Legal caps: 40h/month · 200h/year (Art. 107). Each OT batch requires employee consent.')}</div></div>
        <button className="btn btn-primary" onClick={() => setOpen(true)}><i className="bi bi-plus-lg"></i>{L('Đăng ký OT', 'New OT request')}</button></div>
      {ot && (
        <div className="card" style={{ marginBottom:14 }}>
          <div className="card-head"><h3>{L('OT của tôi so với trần luật', 'My OT vs legal caps')}</h3></div>
          <div className="card-body flex g16 wrap"><OtMeter label={L('Tháng này', 'This month')} {...ot.month} /><OtMeter label={L('Năm ' + today.slice(0, 4), 'Year ' + today.slice(0, 4))} {...ot.year} /></div>
        </div>
      )}
      {canManage && (
        <div className="card" style={{ marginBottom:14 }}>
          <div className="card-head"><h3>{L('Chờ duyệt', 'Pending')}</h3>{pending.length > 0 && <span className="badge badge-warning">{pending.length}</span>}</div>
          <HrmOtCapChips rows={pending} />
          <HrmReqTable rows={pending} showEmp kind="ot" />
        </div>
      )}
      <div className="card">
        <div className="card-head"><h3>{canManage ? L('Tất cả đơn OT', 'All OT requests') : L('Đơn OT của tôi', 'My OT requests')}</h3></div>
        <HrmReqTable rows={canManage ? ots.filter(r => r.status !== 'pending') : mine} showEmp={canManage} kind="ot" />
      </div>
      {open && <NewHrmRequestModal kind="ot" onClose={() => setOpen(false)} />}
    </div>
  );
}
// Chip cảnh báo trần trên hàng đợi duyệt OT
function HrmOtCapChips({ rows }){
  const { st, data } = useHrm();
  const { lang } = useLang();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const flags = rows.map(r => {
    const emp = data.employees.find(e => e.id === r.employeeId); if (!emp) return null;
    const ot = hrmOtTotals(data.requests, emp, r.startDate, st);
    const mAfter = ot.month.used + (+r.hours || 0), yAfter = ot.year.used + (+r.hours || 0);
    if (mAfter > ot.month.cap) return { id:r.id, cls:'badge-error', text:L(r.id + ': duyệt sẽ vượt ' + ot.month.cap + 'h/tháng (' + mAfter + 'h)', r.id + ': exceeds monthly cap') };
    if (yAfter > ot.year.cap) return { id:r.id, cls:'badge-error', text:L(r.id + ': duyệt sẽ vượt ' + ot.year.cap + 'h/năm (' + yAfter + 'h)', r.id + ': exceeds yearly cap') };
    if (mAfter >= ot.month.cap * 0.8) return { id:r.id, cls:'badge-warning', text:L(r.id + ': chạm ' + mAfter + '/' + ot.month.cap + 'h tháng', r.id + ': near monthly cap') };
    return null;
  }).filter(Boolean);
  if (!flags.length) return null;
  return (
    <div className="flex g6 wrap" style={{ padding:'0 14px 10px' }}>
      {flags.map(f => <span key={f.id} className={'badge ' + f.cls}><i className="bi bi-exclamation-triangle" style={{ marginRight:4 }}></i>{f.text}</span>)}
    </div>
  );
}

/* ---------- Form đơn nghỉ phép / OT (dùng cả cho quick action ở main.jsx) ---------- */
function NewHrmRequestModal({ kind, onClose }){
  const p = usePortal();
  const { lang } = useLang();
  const toast = useToast();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const st = hrmSettings(p.appSettings);
  const data = p.hrm;
  const canManage = p.role === 'admin' || p.role === 'manager';
  const myEmp = hrmMyEmployee(data.employees, p.me);
  const today = todayVN(), year = +today.slice(0, 4);
  const freshInit = { employeeId:myEmp ? myEmp.id : '', leaveType:'annual', startDate:today, endDate:today, days:1, startTime:'18:00', endTime:'20:00', reason:'', consent:false };
  const [f, setF, clearDraft, hadDraft] = useFormDraft('hrm-' + kind, freshInit, true);
  const discardDraft = () => { clearDraft(); setF(freshInit); };
  const close = onClose;   // đóng form GIỮ nháp (xóa chỉ khi gửi xong / "Làm mới")
  const set = (k, v) => setF(s => ({ ...s, [k]:v }));
  const emp = data.employees.find(e => e.id === f.employeeId) || null;
  // ngày nghỉ gợi ý = ngày làm việc trong khoảng (trừ T7/CN + lễ); user chỉnh được (0.5 = nửa ngày)
  React.useEffect(() => { if (kind === 'leave') set('days', hrmWorkingDays(f.startDate, f.endDate, data.holidays) || 0.5); }, [f.startDate, f.endDate]);
  const lt = HRM_LEAVE_TYPES[f.leaveType] || {};
  const hours = kind === 'ot' ? hrmHoursBetween(f.startTime, f.endTime) : 0;
  const nightH = kind === 'ot' ? hrmNightHours(f.startTime, f.endTime, st) : 0;
  const balance = emp && f.leaveType === 'annual' ? hrmLeaveBalance(emp, data.requests, year, st) : null;
  const ot = emp && kind === 'ot' ? hrmOtTotals(data.requests, emp, f.startDate, st) : null;
  const otOver = ot && (ot.month.used + hours > ot.month.cap || ot.year.used + hours > ot.year.cap);
  const submit = () => {
    if (!emp) { toast(L('Chọn nhân viên (tài khoản chưa liên kết hồ sơ)', 'Pick an employee'), 'error'); return; }
    if (f.endDate < f.startDate) { toast(L('Đến ngày phải từ ngày trở đi', 'End date must be ≥ start date'), 'error'); return; }
    if (kind === 'leave' && !(+f.days > 0)) { toast(L('Số ngày nghỉ phải lớn hơn 0', 'Days must be > 0'), 'error'); return; }
    if (kind === 'leave' && lt.maxDays && +f.days > lt.maxDays) { toast(L('Loại nghỉ này tối đa ' + lt.maxDays + ' ngày hưởng lương (Đ115 BLLĐ)', 'Max ' + lt.maxDays + ' paid days for this type'), 'error'); return; }
    if (kind === 'leave' && f.leaveType === 'annual' && balance != null && +f.days > balance) { toast(L('Vượt số phép còn lại (' + balance + ' ngày)', 'Exceeds balance (' + balance + ' days)'), 'error'); return; }
    if (kind === 'ot' && !(hours > 0)) { toast(L('Giờ kết thúc phải sau giờ bắt đầu', 'Invalid time range'), 'error'); return; }
    if (kind === 'ot' && !f.consent) { toast(L('Cần tick "Tôi đồng ý làm thêm giờ" (Đ59 NĐ145)', 'Consent checkbox is required'), 'error'); return; }
    const row = { id:genHrmReqCode(data.requests, kind), employeeId:emp.id, kind,
      leaveType:kind === 'leave' ? f.leaveType : null, startDate:f.startDate, endDate:kind === 'ot' ? f.startDate : f.endDate,
      days:kind === 'leave' ? +f.days : null, hours:kind === 'ot' ? hours : null,
      startTime:kind === 'ot' ? f.startTime : null, endTime:kind === 'ot' ? f.endTime : null,
      isNight:kind === 'ot' && nightH > 0, consent:true, reason:f.reason,
      status:'pending', decidedBy:null, decidedAt:null, decidedNote:'' };
    p.hrmAdd('requests', row);
    clearDraft();
    toast(L('Đã gửi đơn ' + row.id + ' — chờ duyệt', 'Request ' + row.id + ' submitted'), 'success');
    onClose();
  };
  return (
    <Modal title={kind === 'ot' ? L('Đăng ký làm thêm giờ', 'New overtime request') : L('Tạo đơn nghỉ', 'New leave request')} width={520} onClose={close} staticBackdrop
      footer={<><button className="btn" onClick={close}>{L('Hủy', 'Cancel')}</button>
        <button className="btn btn-primary" onClick={submit}><i className="bi bi-send"></i>{L('Gửi đơn', 'Submit')}</button></>}>
      <div className="col g16">
        <DraftNotice show={hadDraft} onDiscard={discardDraft} />
        {canManage && (
          <Field label={L('Nhân viên', 'Employee')}>
            <select className="select" value={f.employeeId} onChange={e => set('employeeId', e.target.value)}>
              <option value="">{L('— Chọn —', '— Pick —')}</option>
              {data.employees.filter(e => e.status !== 'terminated').map(e => <option key={e.id} value={e.id}>{e.id} · {e.fullName}</option>)}
            </select>
          </Field>
        )}
        {kind === 'leave' && (<>
          <Field label={L('Loại nghỉ', 'Leave type')} hint={lt.maxDays ? L('Tối đa ' + lt.maxDays + ' ngày hưởng lương (Đ115 BLLĐ)', 'Max ' + lt.maxDays + ' paid days') : lt.deducts === false ? L('Không trừ quỹ phép năm', 'Does not deduct annual quota') : null}>
            <select className="select" value={f.leaveType} onChange={e => set('leaveType', e.target.value)}>
              {Object.entries(HRM_LEAVE_TYPES).map(([id, v]) => <option key={id} value={id}>{v[lang]}</option>)}
            </select>
          </Field>
          <div className="flex g12">
            <div style={{ flex:1 }}><Field label={L('Từ ngày', 'From')}><DateInput value={f.startDate} onChange={v => set('startDate', v)} /></Field></div>
            <div style={{ flex:1 }}><Field label={L('Đến ngày', 'To')}><DateInput value={f.endDate} onChange={v => set('endDate', v)} /></Field></div>
            <div style={{ flex:1 }}><Field label={L('Số ngày', 'Days')} hint={lt.half ? L('Cho phép nửa ngày (0.5)', 'Half-day allowed (0.5)') : null}>
              <input type="number" step="0.5" min="0.5" className="input mono" value={f.days} onChange={e => set('days', e.target.value)} /></Field></div>
          </div>
          {f.leaveType === 'annual' && balance != null && (
            <div className={'badge ' + (+f.days > balance ? 'badge-error' : 'badge-success')} style={{ alignSelf:'flex-start' }}>
              <i className="bi bi-calendar-check" style={{ marginRight:4 }}></i>{L('Phép còn lại: ' + balance + ' ngày', 'Balance: ' + balance + ' days')}</div>
          )}
        </>)}
        {kind === 'ot' && (<>
          <div className="flex g12">
            <div style={{ flex:1.2 }}><Field label={L('Ngày làm thêm', 'OT date')}><DateInput value={f.startDate} onChange={v => set('startDate', v)} /></Field></div>
            <div style={{ flex:1 }}><Field label={L('Bắt đầu', 'Start')}><input type="time" className="input" value={f.startTime} onChange={e => set('startTime', e.target.value)} /></Field></div>
            <div style={{ flex:1 }}><Field label={L('Kết thúc', 'End')}><input type="time" className="input" value={f.endTime} onChange={e => set('endTime', e.target.value)} /></Field></div>
          </div>
          <div className="flex g8 wrap items-center">
            <span className="badge badge-neutral"><i className="bi bi-stopwatch" style={{ marginRight:4 }}></i>{hours}h</span>
            {nightH > 0 && <span className="badge badge-info"><i className="bi bi-moon" style={{ marginRight:4 }}></i>{L('Có ' + nightH + 'h giờ đêm (22h–6h, phụ cấp +30%)', nightH + 'h night hours (22:00–06:00)')}</span>}
            {ot && <span className={'badge ' + (otOver ? 'badge-error' : ot.month.level === 'warn' ? 'badge-warning' : 'badge-success')}>
              <i className="bi bi-speedometer2" style={{ marginRight:4 }}></i>{L('Sau đơn này: ' + (ot.month.used + hours) + '/' + ot.month.cap + 'h tháng · ' + (ot.year.used + hours) + '/' + ot.year.cap + 'h năm', 'After: ' + (ot.month.used + hours) + '/' + ot.month.cap + 'h month')}</span>}
          </div>
        </>)}
        <Field label={L('Lý do', 'Reason')}>
          <textarea className="input" rows={2} value={f.reason} onChange={e => set('reason', e.target.value)} placeholder={kind === 'ot' ? L('Công việc cần làm thêm…', 'What needs overtime…') : L('Lý do nghỉ…', 'Reason…')} />
        </Field>
        {kind === 'ot' && (
          <label className="flex items-center g8" style={{ cursor:'pointer', fontSize:'var(--fs-base)' }}>
            <input type="checkbox" checked={f.consent} onChange={e => set('consent', e.target.checked)} style={{ width:15, height:15, flex:'none' }} />
            <span>{L('Tôi đồng ý làm thêm giờ theo đợt này (bắt buộc — Điều 59 Nghị định 145/2020).', 'I consent to this overtime batch (required — Art. 59, Decree 145/2020).')}</span>
          </label>
        )}
      </div>
    </Modal>
  );
}

/* ---------- Báo cáo (quản lý) → hrm-reports.jsx (nạp SAU file này; tham chiếu qua global) ---------- */

/* ---------- Thiết lập → tab Tổ chức: phòng ban · chức danh (checkbox cấp quản lý) ---------- */
// `f` (state form của HrmSettings) đóng vai settings đang sửa → hrmDepts đọc live không cần refetch.
function HrmOrgSettings({ f, onCommit }){
  const { data, canManage } = useHrm();
  const { lang } = useLang();
  const toast = useToast();
  const confirm = useConfirm();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const depts = hrmDepts(f, data.employees);   // chưa khai báo → gợi ý sẵn từ hồ sơ; thao tác đầu tiên sẽ chốt vào cấu hình
  const [name, setName] = React.useState('');
  const addDept = () => {
    const n = name.trim(); if (!n) return;
    if (depts.some(d => d.name.toLowerCase() === n.toLowerCase())) { toast(L('Phòng ban đã có', 'Department exists'), 'error'); return; }
    onCommit([...depts, { id:hrmUid('pb'), name:n, positions:[] }]); setName('');
  };
  const delDept = async (d) => {
    if (await confirm({ message:L('Xóa phòng ban "' + d.name + '" khỏi danh mục? Hồ sơ nhân viên đã lưu không bị đổi.', 'Remove "' + d.name + '" from the catalog? Saved records stay unchanged.'), confirmLabel:L('Xóa', 'Delete') }))
      onCommit(depts.filter(x => x.id !== d.id));
  };
  return (<>
    <div className="card" style={{ gridColumn:'1 / -1' }}>
      <div className="card-body flex items-center g12 wrap">
        <div className="col g4" style={{ flex:'1 1 380px' }}>
          <div style={{ fontWeight:600 }}>{L('Cơ cấu tổ chức: phòng ban & chức danh', 'Org structure: departments & positions')}</div>
          <div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>
            {L('Danh mục này đổ ra ô Phòng ban / Vị trí ở mọi form nhân sự. Tick "Cấp quản lý" cho chức danh giám đốc, phó giám đốc… — ai đang giữ chức danh đó là quản lý của phòng. Đổi tên/xóa không sửa hồ sơ đã lưu. Mọi thao tác lưu tự động.',
               'This catalog feeds the Department / Position fields on all HR forms. Tick "Manager" on director-level positions — whoever holds one is that department\'s manager. Changes save automatically.')}</div>
        </div>
        {canManage && <div className="flex g8" style={{ flex:'1 1 260px', height:'var(--control-h)' }}>
          <input className="input" value={name} onChange={e => setName(e.target.value)} placeholder={L('Tên phòng ban mới…', 'New department…')} onKeyDown={e => { if (e.key === 'Enter') addDept(); }} />
          <button className="btn btn-primary" style={{ flex:'none' }} onClick={addDept}><i className="bi bi-plus-lg"></i>{L('Thêm phòng ban', 'Add')}</button>
        </div>}
      </div>
    </div>
    {depts.length === 0 && <div className="card"><div className="card-body"><EmptyState icon="bi-diagram-3" text={L('Chưa có phòng ban nào — thêm ở ô trên.', 'No departments yet.')} /></div></div>}
    {depts.map(d => <HrmDeptCard key={d.id} d={d} onChange={patch => onCommit(depts.map(x => x.id === d.id ? { ...x, ...patch } : x))} onDelete={() => delDept(d)} />)}
  </>);
}
function HrmDeptCard({ d, onChange, onDelete }){
  const { data, canManage } = useHrm();
  const { lang } = useLang();
  const toast = useToast();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const [name, setName] = React.useState(d.name);
  React.useEffect(() => { setName(d.name); }, [d.name]);
  const [pos, setPos] = React.useState('');
  const items = hrmPosItems(d);
  const staffCount = data.employees.filter(e => e.dept === d.name && e.status !== 'terminated').length;
  const mgrNames = data.employees.filter(e => e.dept === d.name && e.status !== 'terminated' && items.some(x => x.mgr && x.name === e.position)).map(e => e.fullName);
  const addPos = () => {
    const v = pos.trim(); if (!v) return;
    if (items.some(x => x.name.toLowerCase() === v.toLowerCase())) { toast(L('Chức danh đã có', 'Position exists'), 'error'); return; }
    onChange({ positions:[...items, { name:v, mgr:false }] }); setPos('');
  };
  const setItem = (i, patch) => onChange({ positions:items.map((x, j) => j === i ? { ...x, ...patch } : x) });
  return (
    <div className="card">
      <div className="card-head" style={{ gap:8 }}>
        {canManage
          ? <input className="input" value={name} onChange={e => setName(e.target.value)} style={{ fontWeight:700, minWidth:0 }}
              onBlur={() => { const t = name.trim(); if (t && t !== d.name) onChange({ name:t }); else setName(d.name); }} />
          : <h3>{d.name}</h3>}
        <span className="badge badge-neutral" style={{ flex:'none' }}>{staffCount} {L('NV', 'staff')}</span>
        {canManage && <button className="iconbtn" title={L('Xóa phòng ban', 'Delete department')} onClick={onDelete}><i className="bi bi-trash" style={{ fontSize:12 }}></i></button>}
      </div>
      <div className="card-body col g8">
        <div className="flex items-center g8" style={{ fontSize:'var(--fs-sm)' }}>
          <span className="subtle grow" style={{ fontWeight:600 }}>{L('Chức vụ / chức danh', 'Positions')}</span>
          <span className="subtle" style={{ flex:'none', width:84, textAlign:'center' }}>{L('Cấp quản lý', 'Manager')}</span>
          {canManage && <span style={{ flex:'none', width:24 }}></span>}
        </div>
        {items.length === 0 && <div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{L('Chưa khai báo — form sẽ gợi ý theo chức danh đang dùng trong phòng.', 'None yet — forms suggest from titles in use.')}</div>}
        {items.map((x, i) => (
          <div key={i} className="flex items-center g8">
            <span className="grow" style={{ fontWeight:500, minWidth:0, overflowWrap:'anywhere' }}>{x.name}</span>
            <span style={{ flex:'none', width:84, textAlign:'center' }}>
              <input type="checkbox" checked={!!x.mgr} disabled={!canManage} onChange={e => setItem(i, { mgr:e.target.checked })} /></span>
            {canManage && <button className="iconbtn" title={L('Xóa chức danh', 'Remove')} style={{ flex:'none' }}
              onClick={() => onChange({ positions:items.filter((_, j) => j !== i) })}><i className="bi bi-x-lg" style={{ fontSize:12 }}></i></button>}
          </div>
        ))}
        {canManage && (
          <div className="flex g8" style={{ marginTop:2 }}>
            <input className="input" value={pos} onChange={e => setPos(e.target.value)} placeholder={L('Thêm chức danh…', 'Add position…')} onKeyDown={e => { if (e.key === 'Enter') addPos(); }} />
            <button className="btn" style={{ flex:'none' }} onClick={addPos}><i className="bi bi-plus-lg"></i></button>
          </div>
        )}
        {mgrNames.length > 0 && (
          <div className="subtle" style={{ fontSize:'var(--fs-sm)', paddingTop:6, borderTop:'1px solid var(--border-light)' }}>
            <i className="bi bi-person-badge" style={{ marginRight:4 }}></i>{L('Quản lý hiện tại: ', 'Current managers: ')}{mgrNames.join(' · ')}
          </div>
        )}
      </div>
    </div>
  );
}

/* ---------- Thiết lập ---------- */
function HrmSettings(){
  const { p, st, canManage, data } = useHrm();
  const { lang } = useLang();
  const toast = useToast();
  const confirm = useConfirm();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const [tab, setTab] = React.useState('org');
  const [f, setF] = React.useState({ ...st });
  const [hol, setHol] = React.useState({ date:'', vi:'' });
  const set = (k, v) => setF(s => ({ ...s, [k]:v }));
  // Mọi tab ghi CHUNG 1 blob 'hrm' → luôn lưu từ `f` đầy đủ, tránh tab này ghi đè mất field tab kia.
  const saveBlob = (s) => p.setSetting('hrm', JSON.stringify({ ...s, hoursPerDay:+s.hoursPerDay || 8, hoursPerWeek:+s.hoursPerWeek || 48, nightFrom:+s.nightFrom || 22, nightTo:+s.nightTo || 6, otCapMonth:+s.otCapMonth || 40, otCapYear:+s.otCapYear || 200, leaveBase:+s.leaveBase || 12, shiftStart:(s.shiftStart || '').trim() || '08:00', shiftEnd:(s.shiftEnd || '').trim() || '17:30', region:+s.region || 1, personalRelief:+s.personalRelief || 11000000, dependentRelief:+s.dependentRelief || 4400000, payPeriodStart:Math.min(28, Math.max(1, +s.payPeriodStart || 1)), contractNoFormat:(s.contractNoFormat || '').trim() || HRM_CONTRACT_NO_FMT, appendixNoFormat:(s.appendixNoFormat || '').trim() || HRM_APPENDIX_NO_FMT }));
  const save = () => { saveBlob(f); toast(L('Đã lưu cấu hình', 'Settings saved'), 'success'); };
  // Tab Tổ chức lưu NGAY mỗi thao tác (như CatalogEditor) — cập nhật state cục bộ + ghi blob nền.
  const commitDepts = (next) => { const nf = { ...f, depts:next }; setF(nf); saveBlob(nf); };
  const noFmt = f.contractNoFormat || HRM_CONTRACT_NO_FMT, plFmt = f.appendixNoFormat || HRM_APPENDIX_NO_FMT;
  const noPreview = hrmDocNo(noFmt, { date:todayVN(), seq:7, pad:2, employeeId:'NV-004' });
  const plPreview = hrmDocNo(plFmt, { date:todayVN(), seq:1, pad:2, employeeId:'NV-004', parentNo:noPreview });
  const TABS = [
    { id:'org', icon:'bi-diagram-3', label:L('Tổ chức', 'Organization') },
    { id:'work', icon:'bi-clock-history', label:L('Giờ làm & OT', 'Hours & OT') },
    { id:'pay', icon:'bi-cash-coin', label:L('Lương & thuế', 'Payroll & tax') },
    { id:'contract', icon:'bi-file-earmark-text', label:L('Số hợp đồng', 'Contract no.') },
    { id:'scan', icon:'bi-qr-code-scan', label:L('Quét CCCD', 'ID scanning') },
    { id:'catalog', icon:'bi-list-ul', label:L('Danh mục', 'Catalogs') },
    { id:'holidays', icon:'bi-flag', label:L('Ngày lễ', 'Holidays'), count:data.holidays.length },
  ];
  const addHoliday = () => {
    if (!hol.date || !hol.vi.trim()) { toast(L('Nhập ngày và tên ngày lễ', 'Enter date and name'), 'error'); return; }
    if (data.holidays.some(h => h.date === hol.date)) { toast(L('Ngày này đã có trong lịch lễ', 'Date already exists'), 'error'); return; }
    p.hrmAdd('holidays', { id:hrmUid('le'), date:hol.date, name:{ vi:hol.vi.trim(), en:hol.vi.trim() }, paid:true });
    setHol({ date:'', vi:'' }); toast(L('Đã thêm ngày lễ', 'Holiday added'), 'success');
  };
  return (
    <div className="page pagefade">
      <div className="page-head"><div><h1>{L('Thiết lập nhân sự', 'HR settings')}</h1>
        <div className="sub">{L('Giờ chuẩn để cấu hình (đề xuất 44h/tuần chưa thành luật — mặc định 48h). Chỉ quản trị viên lưu được.', 'Standard hours are configurable (48h default). Only admins can save.')}</div></div></div>
      <Tabs tabs={TABS} active={tab} onChange={setTab} />
      <div className="settings-grid" style={{ display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(320px, 1fr))', gap:14, alignItems:'start', marginTop:14 }}>
        {tab === 'org' && <HrmOrgSettings f={f} onCommit={commitDepts} />}
        {tab === 'work' && (
        <div className="card">
          <div className="card-head"><h3>{L('Giờ làm việc & trần OT', 'Working hours & OT caps')}</h3></div>
          <div className="card-body col g12">
            <div className="flex g12">
              <div style={{ flex:1 }}><Field label={L('Giờ chuẩn/ngày', 'Hours/day')} hint={L('Đ105 BLLĐ: ≤8h', 'Art. 105: ≤8h')}><input type="number" className="input mono" value={f.hoursPerDay} onChange={e => set('hoursPerDay', e.target.value)} /></Field></div>
              <div style={{ flex:1 }}><Field label={L('Giờ chuẩn/tuần', 'Hours/week')} hint={L('Đ105 BLLĐ: ≤48h', 'Art. 105: ≤48h')}><input type="number" className="input mono" value={f.hoursPerWeek} onChange={e => set('hoursPerWeek', e.target.value)} /></Field></div>
            </div>
            <div className="flex g12">
              <div style={{ flex:1 }}><Field label={L('Giờ đêm từ (giờ)', 'Night from (h)')} hint={L('Đ106: 22h', 'Art. 106: 22')}><input type="number" className="input mono" value={f.nightFrom} onChange={e => set('nightFrom', e.target.value)} /></Field></div>
              <div style={{ flex:1 }}><Field label={L('Giờ đêm đến (giờ)', 'Night to (h)')} hint={L('Đ106: 6h', 'Art. 106: 6')}><input type="number" className="input mono" value={f.nightTo} onChange={e => set('nightTo', e.target.value)} /></Field></div>
            </div>
            <div className="flex g12">
              <div style={{ flex:1 }}><Field label={L('Trần OT/tháng (h)', 'OT cap/month')} hint={L('Đ107: 40h', 'Art. 107: 40h')}><input type="number" className="input mono" value={f.otCapMonth} onChange={e => set('otCapMonth', e.target.value)} /></Field></div>
              <div style={{ flex:1 }}><Field label={L('Trần OT/năm (h)', 'OT cap/year')} hint={L('200h · 300h ngành đặc thù', '200h · 300h special sectors')}><input type="number" className="input mono" value={f.otCapYear} onChange={e => set('otCapYear', e.target.value)} /></Field></div>
            </div>
            <div className="flex g12">
              <div style={{ flex:1 }}><Field label={L('Giờ vào ca', 'Shift start')} hint={L('Mốc tính ĐI TRỄ ở báo cáo', 'Late threshold in reports')}><input type="time" className="input" value={f.shiftStart || '08:00'} onChange={e => set('shiftStart', e.target.value)} /></Field></div>
              <div style={{ flex:1 }}><Field label={L('Giờ tan ca', 'Shift end')} hint={L('Mốc tính VỀ SỚM ở báo cáo', 'Early-leave threshold')}><input type="time" className="input" value={f.shiftEnd || '17:30'} onChange={e => set('shiftEnd', e.target.value)} /></Field></div>
            </div>
            <Field label={L('Phép năm cơ bản (ngày)', 'Base annual leave (days)')} hint={L('Đ113: 12 · 14 nặng nhọc · 16 đặc biệt', 'Art. 113: 12 / 14 / 16')}><input type="number" className="input mono" value={f.leaveBase} onChange={e => set('leaveBase', e.target.value)} style={{ maxWidth:180 }} /></Field>
            <button className="btn btn-primary" style={{ alignSelf:'flex-start' }} onClick={save}><i className="bi bi-check-lg"></i>{L('Lưu cấu hình', 'Save')}</button>
          </div>
        </div>
        )}
        {tab === 'pay' && (
        <div className="card">
          <div className="card-head"><h3>{L('Lương & thuế', 'Payroll & tax')}</h3></div>
          <div className="card-body col g12">
            <Field label={L('Vùng lương tối thiểu', 'Min-wage region')} hint={L('NĐ 293/2025 — dùng cho trần đóng BHXH & lương giờ tối thiểu', 'Decree 293/2025')}>
              <select className="select" value={f.region} onChange={e => set('region', e.target.value)}>
                {[1, 2, 3, 4].map(r => <option key={r} value={r}>{L('Vùng ', 'Region ') + r + ' · ' + (HRM_REGION_MIN_WAGE[r].month).toLocaleString('vi-VN') + '₫/' + L('tháng', 'mo')}</option>)}
              </select>
            </Field>
            <Field label={L('Ngày bắt đầu kỳ lương (1–28)', 'Pay period start day (1–28)')}
              hint={L('1 = theo tháng dương lịch. Đặt 15 → kỳ tháng 7 chạy từ 15/06 đến 14/07. Áp cho kỳ TẠO MỚI; kỳ đã tạo giữ nguyên mốc cũ.', '1 = calendar month. 15 → July period runs 15/06–14/07. Applies to NEW payruns only.')}>
              <input type="number" min="1" max="28" className="input mono" style={{ maxWidth:120 }} value={f.payPeriodStart || 1} onChange={e => set('payPeriodStart', e.target.value)} />
            </Field>
            {(() => { const ym = todayVN().slice(0, 7), r = hrmPayPeriodRange(ym, { payPeriodStart:+f.payPeriodStart || 1 }); return (
              <div className="flex items-center g8" style={{ fontSize:'var(--fs-base)' }}>
                <span className="subtle">{L('Ví dụ — kỳ lương tháng ' + ym.slice(5) + '/' + ym.slice(0, 4) + ':', 'Example — period ' + ym.slice(5) + '/' + ym.slice(0, 4) + ':')}</span>
                <span className="mono" style={{ fontWeight:600, color:'var(--primary)' }}>{fmtDate(r.from)} → {fmtDate(r.to)}</span>
              </div>); })()}
            <div className="flex g12">
              <div style={{ flex:1 }}><Field label={L('Giảm trừ bản thân/tháng', 'Personal relief/mo')} hint={L('Hiện hành: 11.000.000₫', 'Currently 11M')}><MoneyInput value={f.personalRelief} onChange={v => set('personalRelief', v)} /></Field></div>
              <div style={{ flex:1 }}><Field label={L('Giảm trừ/người phụ thuộc', 'Relief/dependent')} hint={L('Hiện hành: 4.400.000₫', 'Currently 4.4M')}><MoneyInput value={f.dependentRelief} onChange={v => set('dependentRelief', v)} /></Field></div>
            </div>
            <div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{L('Phụ cấp miễn thuế đặt theo TỪNG khoản trong hồ sơ nhân viên (tick "Miễn thuế" + trần miễn, vd ăn trưa 730.000₫).', 'Tax-free allowances are set per item on each employee record.')}</div>
            <div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{L('Hệ số BHXH (10.5%), biểu thuế TNCN lũy tiến và ma trận làm thêm giờ (150/200/300% + đêm) là hằng số theo luật — cập nhật khi luật đổi.', 'SI rates, PIT brackets & OT multipliers are legal constants.')}</div>
            <button className="btn btn-primary" style={{ alignSelf:'flex-start' }} onClick={save}><i className="bi bi-check-lg"></i>{L('Lưu cấu hình', 'Save')}</button>
          </div>
        </div>
        )}
        {tab === 'contract' && (
        <div className="card">
          <div className="card-head"><h3>{L('Số hợp đồng lao động & phụ lục', 'Contract & appendix numbering')}</h3></div>
          <div className="card-body col g12">
            <Field label={L('Mẫu số HĐ lao động', 'Employment contract no. format')}
              hint={L('Mã dùng được: {SEQ} số thứ tự trong năm · YYYY/YY năm · MM tháng · DD ngày · {NV} mã nhân viên', 'Tokens: {SEQ} · YYYY/YY · MM · DD · {NV}')}>
              <input className="input mono" value={noFmt} onChange={e => set('contractNoFormat', e.target.value)} placeholder={HRM_CONTRACT_NO_FMT} />
            </Field>
            <div className="flex items-center g8" style={{ fontSize:'var(--fs-base)' }}>
              <span className="subtle">{L('Ví dụ:', 'Example:')}</span><span className="mono" style={{ fontWeight:600, color:'var(--primary)' }}>{noPreview}</span></div>
            <Field label={L('Mẫu số phụ lục', 'Appendix no. format')}
              hint={L('Thêm mã {HD} = số HĐ gốc; {SEQ} đếm riêng theo từng HĐ (PL01, PL02…)', 'Extra token {HD} = parent contract no.; {SEQ} counts per contract')}>
              <input className="input mono" value={plFmt} onChange={e => set('appendixNoFormat', e.target.value)} placeholder={HRM_APPENDIX_NO_FMT} />
            </Field>
            <div className="flex items-center g8" style={{ fontSize:'var(--fs-base)' }}>
              <span className="subtle">{L('Ví dụ:', 'Example:')}</span><span className="mono" style={{ fontWeight:600, color:'var(--primary)' }}>{plPreview}</span></div>
            <div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{L('Số được cấp tự động khi thêm HĐ/phụ lục và tự nhảy qua số đã dùng; vẫn gõ đè được để nhập hợp đồng cũ. Trùng số sẽ bị chặn ngay tại form.', 'Numbers are auto-assigned (skipping used ones) and can be overridden; duplicates are blocked in the form.')}</div>
            <button className="btn btn-primary" style={{ alignSelf:'flex-start' }} onClick={save}><i className="bi bi-check-lg"></i>{L('Lưu cấu hình', 'Save')}</button>
          </div>
        </div>
        )}
        {tab === 'scan' && (
        <div className="card">
          <div className="card-head"><h3>{L('Cách quét mã QR trên thẻ CCCD', 'ID card QR scanning')}</h3></div>
          <div className="card-body col g12">
            <Field label={L('Cơ chế quét', 'Scan method')}
              hint={L('Áp dụng cho cả hệ thống (mọi máy). Ô nhập chuỗi cho máy quét cầm tay luôn có ở mọi chế độ.', 'Applies system-wide.')}>
              <select className="select" value={f.cccdScanMode || 'auto'} onChange={e => set('cccdScanMode', e.target.value)}>
                {Object.keys(CCCD_SCAN_MODES).map(k => <option key={k} value={k}>{CCCD_SCAN_MODES[k][lang] || CCCD_SCAN_MODES[k].vi}</option>)}
              </select>
            </Field>
            <div className="col g6" style={{ fontSize:'var(--fs-sm)' }}>
              <div className="subtle"><b>{L('Tự động', 'Auto')}</b>{L(' — máy có camera (điện thoại/tablet) thì bật camera quét liên tục; máy bàn không camera thì dùng ảnh. Nên chọn cái này.', '')}</div>
              <div className="subtle"><b>{L('Ưu tiên camera', 'Camera first')}</b>{L(' — luôn mở camera trước, kể cả trên máy tính có webcam.', '')}</div>
              <div className="subtle"><b>{L('Chỉ ảnh chụp/tệp', 'Image only')}</b>{L(' — không mở camera. Dùng khi máy không được cấp quyền camera hoặc HR đã có sẵn ảnh scan thẻ.', '')}</div>
              <div className="subtle"><b>{L('Chỉ ô nhập chuỗi', 'Text only')}</b>{L(' — dành cho nơi dùng máy quét mã cầm tay (máy tự gõ chuỗi vào ô).', '')}</div>
            </div>
            <div style={{ padding:'8px 12px', borderRadius:6, fontSize:'var(--fs-sm)', background:'color-mix(in srgb, var(--warning) 10%, transparent)' }}>
              <i className="bi bi-info-circle" style={{ marginRight:5, color:'var(--warning)' }}></i>
              {L('Mã QR trên CCCD rất nhỏ (~1,5cm): chụp ảnh dễ trượt vì mờ/lệch nét. Camera quét liên tục nhiều khung/giây và có zoom nên đọc chắc hơn — nhớ đưa mã vào khung ngắm và lại gần ~10cm.',
                 'The ID QR is tiny (~1.5cm): a single photo often fails. Live camera scans many frames per second and can zoom.')}
            </div>
            <button className="btn btn-primary" style={{ alignSelf:'flex-start' }} onClick={save}><i className="bi bi-check-lg"></i>{L('Lưu cấu hình', 'Save')}</button>
          </div>
        </div>
        )}
        {tab === 'catalog' && (<>
        <div className="card">
          <div className="card-head"><h3>{L('Loại hồ sơ scan', 'Document types')}</h3></div>
          <div className="card-body col g12">
            <div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{L('Danh mục dùng khi tải hồ sơ lên trong từng nhân viên. Các loại CCCD · Sơ yếu lý lịch · Hợp đồng đã ký là bắt buộc — thiếu thì hồ sơ nhân viên hiện cảnh báo.', 'Used when uploading employee documents. ID card · CV · Signed contract are required.')}</div>
            <CatalogEditor catalogKey="hrmDocTypes" title={L('Loại hồ sơ', 'Document types')} />
          </div>
        </div>
        <div className="card">
          <div className="card-head"><h3>{L('Quan hệ nhân thân', 'Family relations')}</h3></div>
          <div className="card-body col g12">
            <div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{L('Danh sách chọn ở ô "Quan hệ" khi thêm người thân trong hồ sơ nhân viên. Quan hệ đã ghi trên hồ sơ cũ vẫn giữ nguyên dù bạn xóa khỏi danh mục.', 'Options for the "Relation" field on employee family records.')}</div>
            <CatalogEditor catalogKey="hrmRelations" title={L('Quan hệ', 'Relations')} stringMode={true} />
          </div>
        </div>
        </>)}
        {tab === 'holidays' && (
        <div className="card">
          <div className="card-head"><h3>{L('Lịch nghỉ lễ', 'Holiday calendar')}</h3><span className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{data.holidays.length} {L('ngày', 'days')}</span></div>
          <div className="card-body col g6">
            {data.holidays.slice().sort((a, b) => a.date < b.date ? -1 : 1).map(h => (
              <div key={h.id} className="list-row" style={{ cursor:'default' }}>
                <span className="mono subtle" style={{ fontSize:'var(--fs-base)', flex:'none' }}>{fmtDate(h.date)}</span>
                <span className="grow" style={{ fontWeight:500 }}>{h.name[lang] || h.name.vi}</span>
                {canManage && <button className="iconbtn" title={L('Xóa', 'Delete')} onClick={async () => { if (await confirm({ message:L('Xóa ngày lễ này?', 'Delete this holiday?'), confirmLabel:L('Xóa', 'Delete') })) p.hrmRemove('holidays', h.id); }}><i className="bi bi-trash" style={{ fontSize:12 }}></i></button>}
              </div>
            ))}
            {canManage && (
              <div className="flex g8 wrap" style={{ marginTop:6, paddingTop:8, borderTop:'1px solid var(--border-light)' }}>
                <div style={{ flex:'0 0 150px' }}><DateInput value={hol.date} onChange={v => setHol(s => ({ ...s, date:v }))} /></div>
                <input className="input" style={{ flex:'1 1 160px' }} value={hol.vi} onChange={e => setHol(s => ({ ...s, vi:e.target.value }))} placeholder={L('Tên ngày lễ…', 'Holiday name…')} onKeyDown={e => { if (e.key === 'Enter') addHoliday(); }} />
                <button className="btn btn-primary" onClick={addHoliday}><i className="bi bi-plus-lg"></i>{L('Thêm', 'Add')}</button>
              </div>
            )}
          </div>
        </div>
        )}
      </div>
    </div>
  );
}

/* ---------- Widget dashboard ---------- */
function HrmWidget(){
  const p = usePortal();
  const { lang } = useLang();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const canManage = p.role === 'admin' || p.role === 'manager';
  const myEmp = hrmMyEmployee(p.hrm.employees, p.me);
  const pending = p.hrm.requests.filter(r => r.status === 'pending');
  const minePending = myEmp ? pending.filter(r => r.employeeId === myEmp.id).length : 0;
  return (
    <WidgetShell wid="people" icon="bi-people" iconColor={HRM_APP.color} title={L('Nhân sự & Chấm công', 'People & Time')}
      onViewAll={() => p.openApp(HRM_APP)} onNew={() => p.quick('hrm')} newLabel={L('Tạo đơn nghỉ', 'New leave request')}>
      <CheckinCard />
      <div className="flex justify-between items-center" style={{ padding:'8px 0 0', borderTop:'1px solid var(--border-light)', marginTop:10 }}>
        <span className="muted">{canManage ? L('Đơn chờ duyệt', 'Pending requests') : L('Đơn của tôi chờ duyệt', 'My pending requests')}</span>
        <span className="mono" style={{ fontWeight:700, fontSize:'var(--fs-lg)', color:(canManage ? pending.length : minePending) > 0 ? 'var(--warning)' : 'var(--text-muted)' }}>{canManage ? pending.length : minePending}</span>
      </div>
    </WidgetShell>
  );
}

Object.assign(window, { HrmModule, HrmWidget, CheckinCard, NewHrmRequestModal, useHrm, hrmTimeHM, hrmIsoFrom, hrmWorkingDays, OtMeter, ReqStatusBadge, HrmReqTable });
